Note
No IDE or local setup required. This repository is optimized for fully AI-assisted development using Claude Code. No local toolchain, no IDE, nothing to install — everything works completely through Claude.
Security:
🚀 Fast address finder for Bitcoin and altcoins using OpenCL & Java – includes vanity address generation, balance checking, and offline support.
- About BitcoinAddressFinder
- Requirements
- Quickstart
- Features
- Address Database
- Pages and Projects for Address Lists
- Find Addresses
- Logging and Runtime Statistics
- Collision Probability and Security Considerations
- Similar Projects
- Known Issues
- Future Improvements
- Acknowledgements
- Legal
- License
BitcoinAddressFinder is a free, high-performance tool for scanning random private keys across a wide range of cryptocurrencies — including Bitcoin, Bitcoin Cash, Bitcoin SV, Litecoin, Dogecoin, Dash, Zcash, and many more.
Its core purpose is to generate both compressed and uncompressed addresses with maximum efficiency, combining the portability of the Java Virtual Machine (JVM) with OpenCL-powered GPU acceleration.
Each generated address is checked against a high-speed LMDB database to detect whether it has ever been used — identifying possible balances, known keyspaces, and even RIPEMD160 hash collisions.
🔍 Whether you're generating vanity addresses, verifying address usage, or experimenting with cryptographic edge cases, BitcoinAddressFinder is built for speed, flexibility, and fully offline operation.
🔐 Runs air-gapped · ⚡ GPU-accelerated · 🧪 Unit-tested · 🛠️ Extensible
Made with ❤️ in Germany
Copyright (c) 2017-2025 Bernard Ladenthin
-
Java 21 or newer is required to run BitcoinAddressFinder.
Older versions such as Java 8, 11, or 17 are not supported. -
🚀 OpenCL (optional):
You can run this software in either CPU-only mode or with GPU acceleration via OpenCL for significantly enhanced performance.When OpenCL is enabled:
- Elliptic Curve Key Generation is offloaded to one or more OpenCL-capable devices (e.g., GPUs), dramatically boosting key scanning throughput.
- SHA-256 and RIPEMD-160 hashing operations are also offloaded to the GPU, further reducing CPU load and increasing overall efficiency.
- Multi-GPU setups are fully supported — each device can be configured individually, enabling efficient parallelization and scalability across multiple GPUs.
- Download Software
- Release: Download the binary (jar) from https://github.com/bernardladenthin/BitcoinAddressFinder/releases
- Nightly: Download the binary (jar) newest build from github: https://nightly.link/bernardladenthin/BitcoinAddressFinder/workflows/assembly/main/jar%20binaries.zip
- Download and extract the light database from https://github.com/bernardladenthin/BitcoinAddressFinder#use-my-prepared-database
- Download a configuration set like:
logbackConfiguration.xmlconfig_Find_1OpenCLDevice.json- a launcher for your OS:
- Windows:
run_Find_1OpenCLDevice.bat - Linux / macOS:
run_Find_1OpenCLDevice.sh
- Windows:
- Put all in one directory like the following structure
- Downloads
- lmdb
- data.mdb
- lock.mdb
- bitcoinaddressfinder-1.8.0-SNAPSHOT-jar-with-dependencies.jar
- logbackConfiguration.xml
- config_Find_1OpenCLDevice.js
- run_Find_1OpenCLDevice.bat (Windows) / run_Find_1OpenCLDevice.sh (Linux/macOS)
- lmdb
- Run the launcher for your OS:
- Windows: double-click / run
run_Find_1OpenCLDevice.bat - Linux / macOS:
chmod +x run_Find_1OpenCLDevice.sh && ./run_Find_1OpenCLDevice.sh
Every examples/run_*.bat has a matching examples/run_*.sh (same JVM flags); use the .sh on Linux/macOS and the .bat on Windows.
On startup the tool builds the GPU pre-filter from your database and then begins scanning. The log walks through four one-time build phases — reading → indexing → peeling → assigning — prints a ready line, and from then on emits a periodic Statistics line. That ticking Statistics line is how you know it is running correctly. Abridged real output (one line per phase):
… BinaryFuse8AddressPresence - Binary Fuse8 filter: reading 132288304 addresses from the source …
… BinaryFuse8AddressPresence - Binary Fuse8 filter: reading addresses: 99% (132288300/132288304), 15.4M/s, ETA 0s, elapsed 8s.
… BinaryFuse8AddressPresence - Binary Fuse8 filter: indexing: 99% (132207500/132207501), 22.2M/s, ETA 0s, elapsed 5s.
… BinaryFuse8AddressPresence - Binary Fuse8 filter: peeling: 99% (132207500/132207501), 12.6M/s, ETA 0s, elapsed 10s.
… BinaryFuse8AddressPresence - Binary Fuse8 filter: assigning: 99% (132207500/132207501), 21.1M/s, ETA 0s, elapsed 6s.
… BinaryFuse8AddressPresence - Binary Fuse8 filter: ready (132207501 addresses, 148766720 fingerprint slots).
… ConsumerJava - start consumeKeysRunner
… AbstractProducer - Init producer.
… ConsumerJava - Statistics: [uptime 0 min] [Generated 129 M/s (859 M total)] [-> LMDB 2 M/s (13 M lookups total), 99.22% pre-filtered] … [Hits: 0]
What to look for:
- The four build phases run once, at startup (a few seconds each per 100 M addresses). This is the filter being built from your database — it is normal for the tool to sit here briefly before the first
Statisticsline, with no keys scanned yet. ready (… addresses, … fingerprint slots)means the filter is built and scanning is about to start.Statistics: … [Generated N/s …] [-> LMDB M/s …, NN.NN% pre-filtered] … [Hits: 0]is the running heartbeat.Generatedis the raw key rate;-> LMDBis how many candidates survive the GPU pre-filter and reach the database;pre-filteredis the share the filter eliminated (so the CPU/LMDB never sees them).Hits: 0is expected — a hit means a funded address was actually found.- The filter is named after your
gpuFilterType: the excerpt above is aFUSE_8run, whereas the recommended defaultFUSE_16logsBinary Fuse16 filter: …and drives thepre-filteredshare even higher (fewer survivors reach LMDB). See docs/filter-selection.md for the trade-off.
Every grid and filter recommendation in this README and in docs/filter-selection.md was measured on one developer's hardware. The TuneConfiguration command measures yours:
# Use the launcher — it carries the --add-opens the JVM needs for LMDB access:
examples/run_TuneConfiguration.sh # Linux/macOS
examples\run_TuneConfiguration.bat # WindowsThe bare
java -jar … config_TuneConfiguration.jsonworks only when no real database is configured. The momentlmdbConfigurationReadOnly.lmdbDirectorypoints at an actual LMDB, the lookup path reflects intojava.nio/sun.nio.chand the JVM throwsInaccessibleObjectExceptionwithout the--add-opensflags the launcher sets. Use the launcher, or copy its flag list, whenever you point the tuner at a database.
It sweeps batchSizeInBits × keysPerWorkItem, measures the net end-to-end throughput of each combination (candidate keys per second through the whole pipeline, not kernel time), and prints a table of every arm plus the winning configuration as a ready-to-paste JSON file. It also measures what one database lookup costs on your storage in its current state — the term that ranges from 4.1 µs warm to 292.7 µs cold, and the one the FUSE_8 / FUSE_16 choice hinges on. Every number in the report is labelled MEASURED (here, on this machine) or DOCUMENTED / ESTIMATED (a published constant), so it is always clear which is which.
Honest expectations:
- It takes minutes. The run time is
arms × (warmupSecondsPerArm + secondsPerArm); the defaults give 25 arms × 25 s ≈ 10 minutes, plus a one-time filter build (~44 s per 100 M entries). - It does not read or rebuild your database. The grid sweep builds its pre-filter from a deterministic PRNG source sized to
targetDatabaseEntries— set that to the size of the database you intend to run against. Only the optional verification-cost stage touches LMDB, and only iflmdbConfigurationReadOnly.lmdbDirectorypoints at a real database. With no database configured the command still runs end to end and falls back to the documented lookup cost. sweepFilterTypesis off by default, and should stay off unless you need it. Enabling it forces a second full filter build (~150 s at the 132 M Light DB tier, ~26 minutes at the 1.377 B Full DB tier) to measure theFUSE_8/FUSE_16choice instead of deriving it fromtotal = probe + fpr × verification.
- 📐 Supports blockchain addresses based on secp256k1
- 🛡️ Unit-tested, trusted open source that can be compiled easily by yourself
- 🎯 Vanity generation of Bitcoin addresses using regex patterns
- 🔌 Runs entirely offline — no internet connection is required or used. Suitable for air-gapped systems and isolated environments — even in a bunker with a generator and zero connectivity.
- 🤹 No synchronization required to run multiple instances. Random numbers are used, so no coordinated search strategy is needed — just run it on multiple machines
- ⚡ Checks a high-performance database of known addresses to detect already used ones
- 📦 Portable, platform-independent, runs on the JVM
- 🔁 Generates both uncompressed and compressed keys simultaneously
- 🧮 EC key generation via:
- 🧵 Multiple CPU threads
- 🖥️ Multiple OpenCL devices (optional)
The OpenCL kernel generates keys with the same two techniques the fastest open-source secp256k1 searchers (BitCrack, VanitySearch) use:
- A fixed-base comb computes each work-item's first key
P₀ = k₀·Gfrom a precomputed table of multiples of the base pointG, with almost no point-doublings. - An affine batched-addition walk then produces the remaining
keysPerWorkItem − 1consecutive keys (P₀ + G,P₀ + 2G, …) with cheap point additions, sharing one modular inverse across a whole sub-batch (Montgomery's simultaneous inversion).
This is the hot path of GPU scanning, and it is EC-arithmetic-bound, not hash-bound. It is also deliberately not constant-time (speed over side-channel resistance — see the security note under Find Addresses).
🔧 Tuning the two knobs that control it —
batchSizeInBitsandkeysPerWorkItem— is the single biggest performance decision. The defaults are conservative and not optimal for scanning. For the full mechanism, the staged optimization history with measured benchmarks, per-device tuning guidance, and how to benchmark correctly, see docs/performance.md.
Each Find-mode producer batch covers 2^batchSizeInBits consecutive private keys (the field is a log₂ exponent, not a raw key count). For each batch the CPU:
- Pulls a candidate private key from the configured key producer.
- Aligns the candidate DOWN to a
2^batchSizeInBitsboundary — the lowbatchSizeInBitsbits are cleared so the result is a multiple of2^batchSizeInBits. This produces thesecretBase. Source:KeyUtility.alignDownandAbstractProducer.createSecretBase. - Submits
secretBase(plus the configuredkeysPerWorkItem) to the GPU.
The OpenCL kernel then evaluates secretBase + i for i ∈ [0, 2^batchSizeInBits) in parallel across all GPU work-items, so the batch covers 2^batchSizeInBits consecutive keys cleanly without gap or overlap.
batchSizeInBits |
Keys per batch (2^N) |
Use case |
|---|---|---|
0 |
1 | sequential / secrets-file mode (no batching) — used by config_Find_SecretsFile.json |
14 |
16,384 | per-CPU-producer batch — used by config_Find_8CPUProducer.json and the CPU sides of mixed configs |
18 |
262,144 | typical OpenCL device — used by config_Find_1OpenCLDevice.json |
20–21 |
1M–2M | high-end OpenCL device |
Upper bound: OpenClKernelConstants.BIT_COUNT_FOR_MAX_CHUNKS_ARRAY (= 24) — derived from Integer.MAX_VALUE / CHUNK_SIZE_NUM_BYTES so per-batch result arrays cannot exceed Java's 32-bit array-length limit. Default: 0 (sequential, no batching).
keysPerWorkItem does not change the batch size. It only changes how the same 2^batchSizeInBits work is distributed across GPU work-items:
- With
keysPerWorkItem = 1: the kernel launches2^batchSizeInBitswork-items, each computing one key. - With
keysPerWorkItem = 8: the kernel launches2^batchSizeInBits / 8work-items, each computing 8 keys internally via the affine-addition scalar walker described below.
Constraints (enforced by CProducerOpenCL): keysPerWorkItem must be a power of two, 2^batchSizeInBits must be divisible by keysPerWorkItem, and the total per-launch result buffer (workSize × keysPerWorkItem × chunkSize) must not exceed Integer.MAX_VALUE.
keysPerWorkItem controls how many consecutive keys each GPU work-item generates: it computes the
first key with the fixed-base comb (P₀ = k₀·G) and the rest with the cheap affine-addition walk.
Raising it amortizes the one expensive k₀·G over many cheap steps and shrinks the work-item count
by the same factor — until too few work-items remain to keep the GPU busy. There is therefore a
per-device sweet spot.
- Must be a power of two;
batchSizeInBitsmust be divisible by it. - The default
1is the slowest setting (a fullk·Gper key) — raise it for scanning. - The optimum is device-dependent. On an NVIDIA RTX 3070 Laptop it is
128(~5× faster than1, ~22 M keys/s atbatchSizeInBits=20). After the comb optimization the throughput curve is flat enough that even16–32captures most of the gain on a wide range of GPUs (the example configs use16).
📈 Find your device's sweet spot — and read the benchmarking caveats (laptop GPUs throttle!) — before trusting any number: see docs/performance.md. It has the per-
keysPerWorkItembenchmark tables, the full optimization history with measured gains, theKEYS_BATCH_INVtuning knob, and the exactGridSizeSweepBenchmarkinvocation.
To accelerate elliptic curve multiplication, BitcoinAddressFinder applies a 160-bit private key optimization:
- The upper 96 bits of each 256-bit private key are set to zero
- Only the lower 160 bits are randomized and traversed
This reduction in scalar size speeds up k·G computations, resulting in significantly better performance during brute-force and batch-based key scanning.
✅ Matches the size of
RIPEMD160(SHA256(pubkey))
✅ Especially effective when combined with OpenCL acceleration
As of version 1.6.0, the address presence check is decoupled from the on-disk LMDB store via a small AddressPresence interface. The consumer's scan hot path queries through whichever backend is selected in the config — LMDB itself, a Bloom/Binary-Fuse filter in front of LMDB, or a self-contained in-memory snapshot that lets the LMDB env be closed and garbage-collected after population.
"addressLookupBackend": "LMDB_ONLY"The default is LMDB_ONLY: no in-RAM filter, LMDB stays open and answers every lookup exactly. This is the safest baseline — an exact backend can never report a false positive as a hit — and the in-RAM filters below are opt-in optimisations you add only when you want them. (Note the GPU pre-filter, producerOpenCL.enableGpuFilter, is independent of this setting and works with the LMDB_ONLY default; see below.)
Most users should just run the shipped example
examples/config_Find_GPU_Fuse16Cascade.json — it
is already set to the recommended combination. There are two independent knobs, and the defaults are:
| Knob | Recommended | Why |
|---|---|---|
GPU pre-filter (gpuFilterType, needs enableGpuFilter) |
FUSE_16 |
raises end-to-end throughput 1.2×–7.2×; fits VRAM even at the Full DB tier |
Consumer backend (addressLookupBackend) |
BINARY_FUSE_8 |
small, cheap independent second stage — or LMDB_ONLY for exact, false-positive-free checks |
They form a cascade: the GPU pre-filter decides what crosses PCIe, the consumer backend re-checks
the survivors. Because the two filters hash independently, the second stage rejects what the first
let through — so they must be different filters. Full analysis, with all measurements, in
docs/filter-selection.md and
docs/performance.md.
Why FUSE_16 on the GPU, even though it is not the fastest to probe. Blocked Bloom probes
1.6–2.3× faster on the device, but the device is not the bottleneck — the single consumer thread,
verifying survivors against LMDB, is. A faster probe that passes more candidates (a higher
false-positive rate) just floods that thread. FUSE_16's 0.0016 % FPR hands it 256× less work
than FUSE_8 and removes it as the bottleneck. Measured end-to-end against a real database, only the
GPU filter varied:
| Consumer threads | FUSE_8 net throughput |
FUSE_16 net throughput |
Speed-up |
|---|---|---|---|
| 8 | 188.6 M/s | 227.8 M/s | 1.21× |
| 1 | 31.7 M/s | 228.3 M/s | 7.24× |
The gain grows as the consumer gets tighter — fewer threads, colder storage, or several GPUs feeding one consumer — and is never negative. It costs 2× the VRAM (2.25 vs 1.13 B/entry), which fits an 8 GB card at every tier.
Deviate when:
- CPU-only runs (no GPU):
BINARY_FUSE_16as the consumer backend. Against a cold database its 0.0016 % FPR makes verification cost vanish — the largest single effect in the whole comparison. - Rebuild often, or short on build RAM:
BLOCKED_BLOOM(low density,bpe9–11). It builds 3–4× faster in a single streaming pass, where Binary Fuse peels through ~29 B/entry of auxiliary arrays (~40 GB transient at the Full DB tier). Atbpe≥ 17BINARY_FUSE_16dominates it on both memory and accuracy. It is not offered as a GPU pre-filter on purpose — its faster probe passes more candidates to the consumer, which loses net throughput. LMDB_ONLY— whenever a false positive is unacceptable rather than merely costly.
On GPU,
CL_DEVICE_MAX_MEM_ALLOC_SIZEis a reported floor, not a hard cap. The filter is a single OpenCL allocation. NVIDIA reports a conservative quarter of VRAM (2047 MB on an 8 GB RTX 3070) but the driver actually honours far larger single buffers — a 3.1 GB blocked-bloom filter allocated and ran fine on the 3070 — soBINARY_FUSE_16(3.14 GB) fits the Full DB tier there after all. The robust test is whether the allocation succeeds (the upload path fails loudly if it does not), not the reported number. Run{"command":"OpenCLInfo"}to read it.
All numbers above come from the storage-free bench in
docs/measurements/bench_filters.sh, which measures the
filters without LMDB behind them so the results reproduce across machines. Run it yourself; the
false-positive rates are deterministic and must match to the digit.
Supported values:
| Value | RAM cost | Lookup latency | LMDB stays open? | Best for |
|---|---|---|---|---|
LMDB_ONLY |
minimal (mmap only) | slowest | yes | default — exact, no filter, can never produce a false hit; also the simplest base for GPU pre-filtering |
BLOOM |
~80 MB – ~1.5 GB (FPP) | fast for misses | yes | when you want to skip LMDB for the common miss case but keep it open to verify hits |
HASHSET |
~80 B / entry | fast | no | small databases where memory is plentiful and exact lookup is required by callers |
TRUNCATED_LONG_64 |
~8 B / entry | fast when small, degrades with size | no | best in-RAM trade-off when you do want to drop LMDB and the database is small; latency scales worse than any other backend (see the size sweep) |
BINARY_FUSE_8 |
~1.13 B / entry | fast | yes | ultra-low RAM filter in front of LMDB; 0.4% of filter hits are verified against LMDB (also feeds the GPU pre-filter) |
BINARY_FUSE_16 |
~2.25 B / entry | fast | yes | like Fuse-8 but 0.0015% FPR — fewer LMDB verifications; use when Fuse-8's verification cost is measurable |
BLOCKED_BLOOM |
1.375 B / entry (bpe 11, configurable) | fastest probe | yes | rebuild-heavy workloads — constructs 3× faster and streams in one pass instead of peeling through ~29 B/entry of auxiliary arrays; loses on total cost because its FPR is ~4× higher at equal size |
HASHSET stores the full hash160; TRUNCATED_LONG_64 keeps only the 8 bytes after the bucket byte. Both in-RAM backends use the same 256-bucket layout (one chunk per first-byte value) so per-entry numbers dominate.
| Backend | 100 M entries (operational) | 132 M entries (Light DB) | 1.377 B entries (Full DB) |
|---|---|---|---|
HASHSET |
~8.0 GB | ~10 GB | ~110 GB ❌ |
TRUNCATED_LONG_64 |
~0.8 GB | ~1.1 GB | ~11 GB |
BLOOM (FPP 0.01) |
~120 MB | ~150 MB | ~1.6 GB |
BINARY_FUSE_8 |
~113 MB | ~149 MB | ~1.5 GB |
BINARY_FUSE_16 |
~225 MB | ~298 MB | ~3.1 GB |
BLOCKED_BLOOM |
~138 MB | ~182 MB | ~1.89 GB |
Every row is now exact, reported by each backend's
sizeInBytes()rather than estimated from heap deltas. The old estimates ran ~25 % high (1.412 vs the true 1.126 B/entry for Fuse-8), which made them unusable for a comparison in which memory is a decision criterion.Per-entry constants: Fuse-8 1.126 B, Fuse-16 2.252 B, Blocked Bloom 1.375 B at the default 11 bits/entry. Blocked Bloom's density is configurable via
blockedBloomBitsPerEntryandblockedBloomK; the measured optimum forkis 5/6/7/7/8/9 at 8/11/14/17/21/26 bits per entry. It saturates sub-linearly, so there is no proportional rule — an earlierk ≈ 0.55 × bitsPerEntrywas inferred from the low end of that sweep and overshoots by 2 at the top.Blocked Bloom no longer rounds to power-of-two block counts. It sizes exactly via fastrange (
unsignedMultiplyHigh(hash, numBlocks),mul_hion the GPU). The old rounding wasted up to 2×: a request for 11 bits/entry at 100 M entries delivered 21.47. Per-entry cost is now a constant, not a step function that fell as the database grew.
HASHSET and TRUNCATED_LONG_64 are replacements, not decorators: they are populated once from LMDB via a streaming pass, then the LMDB env is closed and the reference to it is dropped. The on-disk store is no longer needed and the file-backed mmap pages can be reclaimed by the OS. Whether the chain still needs LMDB is reported by the requiresBackend() method on the chosen backend.
TRUNCATED_LONG_64 uses 256 buckets indexed by the first byte of each hash160. A single Java long[] is capped at Integer.MAX_VALUE elements; bucketing keeps every bucket well below that limit and gives O(1) bucket selection plus a much smaller per-bucket binary search. At the Full DB tier each bucket holds ~5.4 M entries.
hash160 is the output of SHA-256 followed by RIPEMD-160 and is cryptographically uniform; any subset of its bits is also uniform. TRUNCATED_LONG_64 keeps only the 8 bytes after the bucket byte (72 bits of total resolution: 8-bit bucket + 64-bit stored value). The probability that a random query collides with one of N stored entries is N/2⁶⁴, which for the largest published database tier is ~7.5 × 10⁻¹¹ per query — rare enough that surfacing such a collision as an unverified hit is harmless (LMDB is closed after population, like HASHSET). Unlike the Binary Fuse backends, whose 0.4%/0.0015% FPR is far too high to leave unverified, TRUNCATED_LONG_64 needs no LMDB verifier. In return:
- Cache-line dense: 8 longs per 64-byte cache line, the binary search walks very little memory.
- JDK intrinsic:
Arrays.binarySearch(long[], long)is heavily JIT-optimised and produces branchless comparisons on modern CPUs. - No boxing, no per-entry object header, no pointer chasing — every comparison is a single CPU instruction.
BINARY_FUSE_8 and BINARY_FUSE_16 are Binary Fuse Filters (Graf & Lemire, 2022) — a faithful port of the reference FastFilter construction. Each inserted key derives a single 64-bit hash murmur64(key + seed); its high bits pick a base slot and two low/mid bit-windows offset it into three consecutive, overlapping segments. A simple XOR invariant across those three slots makes lookup a single three-read check:
hash = murmur64(key + seed)
base = unsignedMulHigh(hash, segmentCountLength) // in [0, segmentCountLength)
h0 = base
h1 = base + 1·segmentLength ⊕ ((hash >>> 18) & segmentLengthMask)
h2 = base + 2·segmentLength ⊕ ( hash & segmentLengthMask)
hit ⟺ fingerprints[h0] ⊕ fingerprints[h1] ⊕ fingerprints[h2] == fingerprint(hash)
Unlike a plain XOR filter's three disjoint segments (~1.23× space), the fused overlapping-segment geometry lets construction succeed on the first attempt at only ~1.125× space. Duplicate 64-bit keys (two distinct hash160 sharing their first 8 bytes) cannot be placed, so they are de-duplicated before construction — lossless, since every filter hit is verified against LMDB anyway.
No false negatives — every key inserted via populateFrom is always found, so a filter miss is definitive.
False-positive rate — 1/256 ≈ 0.4% for Fuse-8 (8-bit fingerprint), 1/65536 ≈ 0.0015% for Fuse-16 (16-bit fingerprint). That FPR is far too high to report a filter hit as a final hit (at 0.4% roughly one in every 250 scanned addresses would be a spurious hit), so the filter is used exactly like BLOOM: a miss short-circuits without touching LMDB, a hit falls through to LMDB to confirm the address or reject it as a false positive.
Decorator, not a replacement — BinaryFuseAccelerator wraps the filter plus the LMDB delegate; requiresBackend() returns true, so the LMDB env stays open as the exact verifier (unlike the self-contained HASHSET / TRUNCATED_LONG_64 snapshots, which drop LMDB).
Choose between the two variants based on how expensive the LMDB verification of filter hits is in your workload. For most use cases with a light or full database, BINARY_FUSE_8 gives the best RAM-to-latency trade-off: a tiny in-RAM filter that skips LMDB for the ~99.6% of queries that miss, while BINARY_FUSE_16 drops the FPR ~256× (fewer LMDB verifications) at the cost of 2× the fingerprint array size.
The Binary Fuse filters are excellent at rest (1.13 B/entry is unbeatable) but expensive to construct: peeling holds several auxiliary arrays live at once — the 64-bit keys, per-slot counters, an XOR accumulator, a peeling queue, and the reverse order/alone arrays — roughly 29 bytes per entry at peak, ≈ 42 GB at the 1.377 B-entry Full DB.
Correction. An earlier edition of this document claimed that made
BINARY_FUSE_8unbuildable at the Full DB tier on commodity hardware. That was wrong, and it was the stated reason this backend existed. Independently retested on a 61.6 GB host (cache cleared first,-Xmx48g), Fuse-8 builds in 1 564 s to 1 504 MiB with a 0.393 % FPR and no false negatives. The original observation — a build that appeared to hang — was the I/O-bound ingest stalling under memory pressure, not the peeling step: peeling itself finished in 76 s at ~19 M keys/s. Two different phenomena were conflated.
BLOCKED_BLOOM's remaining edge at that tier is purely build cost — it builds roughly twice as fast — not lookup speed (which does not reproduce across machines) and not because fuse is impossible. On total cost BINARY_FUSE_8 wins (smaller, lower FPR, verified over the database's whole life against a one-time build), so it is the recommended full-tier backend; reach for BLOCKED_BLOOM when builds are frequent or heap during the build is tight.
BLOCKED_BLOOM exists to close exactly that gap. It is a blocked Bloom filter: allocate the bit array once, then stream every address through it setting k bits. There is no peeling, no auxiliary array, no second pass — peak build memory is the filter itself (~2 GB at the Full DB tier), so it builds anywhere the finished filter fits.
Blocked layout — all k bits in one cache line. The bit array is split into 512-bit blocks (64 bytes = one CPU cache line = one coalesced GPU memory transaction). A key's block is chosen from the high bits of one hash; all k of its bit positions are then confined to that block:
key = hash160[0..7] as big-endian long // same extraction as the fuse filters
a = murmur64(key)
b = murmur64(key + GOLDEN)
block = a >>> (64 - logBlocks) // uniform block index
bit_i = ((int)b + i * (int)(b >>> 32)) & 511 // i = 0..k-1, all inside that block
hit ⟺ every bit_i is set
So a lookup touches exactly one 64-byte region instead of the three scattered reads a fuse lookup makes. That is what makes the extra bit-probes nearly free once the filter no longer fits in cache — and it is the property that makes this layout the natural candidate for a future GPU-side filter (one coalesced read per candidate).
No false negatives — every inserted key always passes, so a filter miss is definitive.
False-positive rate — ~0.76 % at the shipped default (k = 6, 11 bits/entry; fastrange sizes it to exactly that density at every tier). Like the fuse filters this is far too high to report unverified, so BlockedBloomAccelerator is a decorator: requiresBackend() returns true, a miss short-circuits, a hit falls through to LMDB to confirm or reject.
Measured against a real database (FilterMeasurementMain, Light DB = 132,288,304 entries; build time, retained heap after GC, and 5 M random-non-member probes; zero false negatives on 20 k sampled real members in every row):
| Backend | build time | retained RAM | B / entry | lookups/s | measured FPR |
|---|---|---|---|---|---|
BLOCKED_BLOOM |
34.8 s | 259.5 MiB | 2.057 | 11.54 M | 0.258 % |
BINARY_FUSE_8 |
68.8 s | 143.4 MiB | 1.137 | 12.93 M | 0.390 % |
BINARY_FUSE_16 |
70.6 s | 287.4 MiB | 2.278 | 12.30 M | 0.002 % |
TRUNCATED_LONG_64 |
41.9 s | 1082.2 MiB | 8.578 | 1.52 M | 0.000 % |
The
BLOCKED_BLOOMrow here was measured before fastrange, at the old power-of-two default (~16 bits/entry). At the current default (11 bits/entry,k = 6) it is smaller — 1.375 B/entry — but has a higher FPR of ~0.76 %; the build-time and lookup columns are representative either way.
Reading the table: BLOCKED_BLOOM builds ~2× faster than either fuse variant (streaming vs. peeling), costs somewhat more RAM at the same density, and looks up in the same ballpark — the differences are modest. Its advantage is confined to build cost and heap at the Full DB tier (a rebuild-heavy or heap-constrained niche), not the overall recommendation — which is BINARY_FUSE_8 on total cost — and not whether fuse can build at all (it can — see below).
Same harness, same machine (64 GB RAM), against the 61 GB / 1,377,478,516-entry database. Blocked Bloom builds it in a 6 GB heap (it only needs the finished filter); the fuse filters build here too, but their multi-pass peeling needs ~40 GB of transient heap (-Xmx48g), so an under-sized heap OOMs mid-peel — that, not any algorithmic limit, is what an earlier version of this document mistook for "fuse cannot build at the Full DB tier". Blocked-Bloom row:
| metric | measured |
|---|---|
| entries | 1,377,478,516 |
| retained RAM | 2,051.8 MiB (2.0 GiB) |
| bytes / entry | 1.562 |
| lookups/s | 9.53 M |
| false-positive rate | 0.485 % |
| false negatives | 0 |
| build time | 1,869 s (31 min, I/O-bound — see below) |
Faster to build than Fuse-8 at this tier — the lookup question is unresolved. Both backends measured on both hosts, each arm preceded by a cache-clearing pass, at -Xmx48g:
| 16 MB L3 host | 96 MB L3 host | |||
|---|---|---|---|---|
BINARY_FUSE_8 |
BLOCKED_BLOOM |
BINARY_FUSE_8 |
BLOCKED_BLOOM |
|
| build | 3 186 s | 1 617 s | 1 564 s | 859 s |
| lookups/s | 9.37 M | 7.44 M | 6.13 M | 7.17 M |
| retained | 1 504 MiB | 2 080 MiB | 1 504 MiB | 2 080 MiB |
| FPR | 0.393 % | 0.485 % | 0.393 % | 0.485 % |
Blocked Bloom builds faster on both machines. The 1.8–2.0× seen here is an LMDB-bound figure; measured storage-free the true algorithmic factor is 3.0× (14.6 s vs 43.8 s at 100 M), because these runs spend most of their time on a scattered 61 GB cursor walk that is common to both filters and dilutes the difference. Fuse-8 is 27 % smaller with a ~19 % better FPR, both bit-identical across hosts.
The lookup row above is an artefact — do not use it. It comes from a single-shot probe loop run right after a multi-GB build, and re-measuring with the warmed, storage-free JMH instrument inverts the answer on both machines. Measured ns/op at 1 B entries:
BINARY_FUSE_8 |
BLOCKED_BLOOM |
|
|---|---|---|
| 16 MB L3 host | 91.7 | 79.8 |
| 96 MB L3 host | 39.9 | 42.7 |
So the ranking flips with cache size: blocked Bloom leads by 13–36 % where L3 is modest, Fuse-8 by ~6 % (intervals overlapping) where it is large. Note the same Fuse-8 array costs 91.7 vs 39.9 ns on the two hosts — 2.3× from cache alone. Full analysis in docs/performance.md.
Recommendation: BINARY_FUSE_8 — see Which filter to pick. Blocked Bloom wins every probe column on this page, and still loses the total: at equal footprint its false-positive rate is ~4× higher, and a false positive costs 4.1 µs warm to 292.7 µs cold against tens of nanoseconds for the probe. The probe is the term that barely counts. Blocked Bloom remains the pick when build time dominates (3× faster, one streaming pass, no ~29 B/entry peeling arrays).
Build time is dominated by LMDB I/O, not by the filter, and the lever is free RAM. The database is written in random hash160 order, so a key-ordered cursor walk is physically scattered across the 61 GB file; with headroom the pages accumulate, without it they are evicted before reuse and re-read. Measured mid-build under memory pressure: the NVMe ran 86 % busy at 332 MB/s while yielding only ~19 MB/s of useful entry data — roughly 17× read amplification. The same build took 1,869 s with ~2 GB free but 1,043 s starting from an emptied cache with 58 GB free — nearly 2× faster from the colder start.
Setting "useNoReadAhead": true opens LMDB with MDB_NORDAHEAD, asking the OS not to prefetch neighbours that get evicted before use. This flag has no effect on Windows — LMDB does not implement it there (see the MDB_NORDAHEAD javadoc in lmdbjava: "The option is not implemented on Windows"), so it is a POSIX-only lever. On Linux it is the standard remedy for the amplification described above; it is currently unvalidated in this project, because the only measurements taken so far were on Windows where it does nothing. It defaults to false. See docs/performance.md for the measurement history and TODO.md for the remaining levers (higher queue depth, key-order compaction).
k (bits set per key) is the accuracy knob. Too few and the filter is too permissive; too many and the shared 512-bit block saturates, raising the FPR again. The optimum is not guessed — it is measured. Since fastrange replaced power-of-two block rounding, the density is exactly what you configure, and the shipped default is DEFAULT_K = 6 at DEFAULT_BITS_PER_ENTRY = 11. At that default density (10 M synthetic entries, 2 M random-non-member probes per row):
k |
4 | 5 | 6 | 7 | 8 | 10 |
|---|---|---|---|---|---|---|
| FPR @ 11 b/e | 0.997 % | 0.805 % | 0.758 % | 0.764 % | 0.806 % | 0.996 % |
More bits is not monotonically better — the FPR bottoms out at k = 6 and climbs again, because every additional probe fills the same 512-bit block faster. And the blocked optimum sits below the textbook unblocked prediction (m/n)·ln2 ≈ 7.6 for 11 b/e: confining all probes to one block introduces per-block load variance, which penalises high k. So DEFAULT_K is an empirical result, not a formula.
The optimum is density-dependent and must be raised together with bitsPerEntry. Measured across the whole surface (10 M entries, fastrange sizing, so the effective density equals the configured one exactly):
| bits/entry | 8 | 11 (default) | 14 | 17 | 21 | 26 |
|---|---|---|---|---|---|---|
optimum k |
5 | 6 | 7 | 7 | 8 | 9 |
| FPR at optimum | 2.44 % | 0.758 % | 0.306 % | 0.153 % | 0.086 % | 0.055 % |
The optimum saturates sub-linearly: the ratio k / bits-per-entry falls from 0.63 at 8 b/e to 0.35 at 26 b/e, so there is no proportional rule. (An earlier k ≈ 0.55 × bits/entry was inferred from the middle of this range — it happens to fit near 11 b/e but overshoots by 2 at the top.) If you raise blockedBloomBitsPerEntry, raise blockedBloomK to the matching column, or you measure a mismatched geometry rather than a denser filter.
k = 1 would be a direct-addressed bitmap — one bit per hash, no shared block, fastest of all — but its FPR is 1 − e^(−n/m) ≈ 6 % at these densities, so roughly 1 query in 17 falls through to LMDB. That verification cost dwarfs any lookup-speed saving, which is why the default is a multi-probe k, not 1.
Because containsAddress short-circuits on the first unset bit, a sparser (lower-density, lower-k) filter answers a miss sooner — lookup speed tracks sparsity, not size. That is the opposite of intuition and matters when tuning: a denser blocked-bloom filter buys a lower FPR but each probe is slightly slower, and the two effects trade off through the cost model (§ docs/filter-selection.md).
This is also the clearest statement of why blocked Bloom is an addition, not a replacement for the fuse filters: at equal footprint the fuse construction always has the lower FPR (e.g. at ~1.13 B/entry, Fuse-8's 0.39 % against blocked bloom's 1.6 %; at ~2.25 B/entry, Fuse-16's 0.0016 % against 0.13 %). Per byte stored, fuse is simply the better filter — blocked bloom's justification is its ~3–4× faster single-pass build and its faster GPU probe, not accuracy per byte.
The probe sequence is bit_i = (x + i·y) mod 512, whose period is 512 / gcd(y, 512). With an unconstrained stride y, 1 key in 512 draws y ≡ 0 (mod 512) and places all k probes on a single bit; 1 in 256 reaches only 2 distinct bits, 1 in 128 only 4. Those degenerate keys are near-useless filter entries and measurably inflate the aggregate FPR — 0.258 % before the fix versus 0.184 % after, at identical size and k. Forcing y odd makes gcd(y, 512) = 1, so the probes are always distinct. Cost: one OR instruction. BlockedBloomAddressPresenceTest#probePositions_areAlwaysDistinct_becauseStrideIsOdd fails if it is ever dropped.
Which consumer backend to choose (addressLookupBackend; the GPU pre-filter is a separate knob — see Which filter to pick):
- Small/medium databases (below ~15 M entries) →
BINARY_FUSE_8. Fastest filter measured at that scale on either machine, and lowest RAM. - Large databases →
BINARY_FUSE_8, on either host. Cache size changes which filter probes faster, but not which one is cheaper overall: verification cost dominates, and Fuse-8's FPR is ~4× lower at equal footprint. Against genuinely cold storageBINARY_FUSE_16pulls ahead (its FPR is two orders of magnitude lower). Budget ~40 GB of transient heap to build Fuse at the 1.377 B tier (the peeling arrays need ~29 B/entry);BLOCKED_BLOOMneeds only the finished 1.89 GB and builds 3× faster, so prefer it if you rebuild often or cannot spare the heap.
Ready-to-run example covering both tiers, on GPU:
examples/config_Find_GPU_Fuse16Cascade.json —
FUSE_16 GPU pre-filter + BINARY_FUSE_8 consumer backend, the recommended cascade (see
Which filter to pick).
For the consumer backend specifically, BINARY_FUSE_8 is the safe simple default on both tiers;
BINARY_FUSE_16 is the better consumer choice against cold storage, where its far lower FPR makes
verification cost vanish. Both fuse widths now have a GPU kernel. Building Fuse at the Full DB tier
needs roughly 40 GB of transient heap for the peeling arrays, so raise -Xmx accordingly; if that
heap is unavailable or you rebuild often, BLOCKED_BLOOM builds 3–4× faster in a single streaming
pass and needs only its finished 1.89 GB.
BLOCKED_BLOOM is therefore an addition, not a replacement — the two filters have genuinely different optimal domains, so neither was removed.
AddressLookupBenchmark (JMH, replaces the old JUnit timing test) writes 2048 random hash160 entries into LMDB, then measures average containsAddress time per backend across 8192 pre-generated miss queries with JMH-driven JIT warmup and fork isolation. Sample numbers from one workstation run — relative ordering is the architecturally interesting part; absolute values vary by machine, JVM, and dataset size:
| Backend | ns / op |
|---|---|
LMDB_ONLY |
833 |
BLOOM |
155 |
HASHSET |
42 |
TRUNCATED_LONG_64 |
40 |
BINARY_FUSE_8 |
~19 |
BINARY_FUSE_16 |
~23 |
BLOCKED_BLOOM |
~30 |
This table is the cache-hot regime and does not generalise — see the scale sweep below. With only 2048 entries every filter fits in L1, so the measurement reduces to instruction count. The ranking changes substantially at real database sizes.
The 2048-entry table above measures one point on a curve, and it is the least representative one. FilterLookupBenchmark sweeps the entry count instead, and removes LMDB entirely — addresses come from a seeded PRNG (PrngAddressIterable), so there is no page-cache behaviour, no read amplification, and no run-to-run variance from what the OS happened to cache. Only the filter is measured. Average ns per containsAddress against non-member probes:
| Backend | 100 K | 1 M | 10 M | 50 M | 100 M |
|---|---|---|---|---|---|
BINARY_FUSE_8 |
13.1 | 18.5 | 31.2 | 52.1 | 59.3 |
BINARY_FUSE_16 |
15.1 | 19.2 | 36.5 | 60.9 | 65.8 |
BLOCKED_BLOOM |
23.9 | 33.2 | 39.8 | 40.5 | 50.2 |
HASHSET |
52.1 | 92.9 | 145.1 | — | — |
TRUNCATED_LONG_64 |
80.1 | 145.4 | 337.3 | 533.3 | 635.5 |
BLOOM |
85.4 | 98.9 | 114.1 | 203.2 | 203.8 |
HASHSET is omitted above 10 M — at ~80 B/entry it needs ~8 GB at 100 M.
There is a crossover — but it is set by L3 cache size, not by the filters. On this reference machine (16 MB L3) it sits between 10 M and 50 M entries: below it BINARY_FUSE_8 wins by ~1.7×; above it BLOCKED_BLOOM wins by ~1.3×. The mechanism is visible in the step sizes: from 10 M to 50 M Fuse-8 degrades by ~1.7× (31.2 → 52.1 ns) while blocked Bloom barely moves (39.8 → 40.5 ns, 1.02×). That is exactly where the Fuse-8 array outgrows L3 cache (~11 MB at 10 M, ~57 MB at 50 M). Once neither filter is cache-resident, a fuse lookup pays three scattered cache misses while a blocked-Bloom lookup pays one — all k probes share a single 512-bit block. Blocked Bloom is the flattest structure in the table (1.9× from 100 K to 100 M, against Fuse-8's 4.5×).
Practical reading: this crossover moves with the cache, so "blocked Bloom wins at production scale" is machine-specific, not universal. On a large-cache host (96 MB L3) Fuse-8 stays ahead through 100 M+, and even at the Light DB tier (132 M) it measures faster (32.8 vs 37.1 ns). The lookup ranking is close and cache-dependent, which is why the recommendation does not rest on it but on total cost (size + FPR + a one-time build, amortised over the database's life) → BINARY_FUSE_8. See docs/performance.md for the second-machine confirmation and the full 500 M / 1 B columns.
TRUNCATED_LONG_64degrades worst of all (7.6× from 100 K to 100 M, ending at 612 ns — 13× slower than blocked Bloom). Its binary search costs ~log₂(n/256) dependent cache misses. The "near-HASHSETlatency" characterisation holds only for small databases.BLOOMis the slowest filter despite scaling flatly (2.4×). See the tuning history below; what remains is structural in Guava.
Run it yourself:
# all backends at one size
java ... org.openjdk.jmh.Main FilterLookupBenchmark -p entries=1000000 -f 1 -wi 3 -i 5
# scale sweep, filters only (HASHSET needs ~80 B/entry, so give it heap or omit it)
java ... org.openjdk.jmh.Main FilterLookupBenchmark \
-p backend=BLOOM,BINARY_FUSE_8,BINARY_FUSE_16,BLOCKED_BLOOM \
-p entries=1000000,10000000,50000000 -f 1 -wi 3 -i 5The default backend is LMDB_ONLY (no in-RAM structure, exact, can never produce a false hit). When you do want to trade RAM for speed, TRUNCATED_LONG_64 reaches near-HASHSET latency at ~10× lower memory cost on small databases — but see the size sweep above: its binary search degrades faster than any other backend, and by 10 M entries it is the slowest option measured. BINARY_FUSE_8 and BINARY_FUSE_16 offer even lower memory with O(1) lookup (3 direct array reads + 1 hash) but keep LMDB open to verify hits; cache-cold latency grows with filter size, but for small-to-medium databases the three-read pattern typically stays in L2 or L3 cache.
Run the backend comparison benchmark yourself with:
mvn test-compile exec:java -Dexec.args="AddressLookupBenchmark"
# Narrow to a single backend
mvn test-compile exec:java \
-Dexec.args="AddressLookupBenchmark -p backend=BINARY_FUSE_8"
# Allocation profile (per-op bytes allocated)
mvn test-compile exec:java -Dexec.args="AddressLookupBenchmark -prof gc"For an isolated view of Binary Fuse Filter construction and lookup without LMDB (L1/L2/L3 cache-size sweep):
mvn test-compile exec:java -Dexec.args="BinaryFuseBenchmark"By default the OpenCL kernel transfers every derived work-item result back to the host, and the consumer then checks each hash160 against the selected addressLookupBackend. With enableGpuFilter, a Binary Fuse 8 filter is instead pushed onto the GPU so the kernel checks each derived hash160 inline and transmits only the candidates the filter marks as possibly present. This collapses the PCIe read-back from the full 2^batchSizeInBits grid down to the handful of hits per batch — the dominant cost on large batches.
The GPU pre-filter is independent of the CPU addressLookupBackend. It is built once during consumer init — while LMDB is still open, before a self-contained backend (HASHSET/TRUNCATED_LONG_64) would close it — purely as a transient VRAM-upload artifact (the host copy is freed after the one-time upload). So it works with every backend, including the LMDB_ONLY default; you do not have to set the CPU backend to BINARY_FUSE_8. The survivors that come back from the GPU are verified directly against the CPU lookup, so they are never "double filtered". (If the CPU backend already is BINARY_FUSE_8, that filter is reused for the upload to avoid a second LMDB scan.)
Enable it on an OpenCL producer (see examples/config_Find_GPUFilterCompact.json):
"consumerJava": {
"lmdbConfigurationReadOnly": { "addressLookupBackend": "LMDB_ONLY" }
},
"producerOpenCL": [
{ "enableGpuFilter": true, "transferAll": false }
]| Field | Default | Effect |
|---|---|---|
enableGpuFilter |
false |
When true, a Binary Fuse 8 filter is built from LMDB during consumer init and uploaded to GPU VRAM (once per session); the kernel filters inline (compact output). Independent of the CPU addressLookupBackend — built while LMDB is open, so it works with every backend including the LMDB_ONLY default. |
transferAll |
false |
Forces full-transfer (legacy) output even when enableGpuFilter is on. Finder sets this to true automatically — with a warning — whenever enableVanity = true, because vanity scanning must see every derived address. |
The two flags are independent, not opposites — a common point of confusion. enableGpuFilter decides whether the filter runs on the GPU; transferAll decides which output layout the kernel uses (send back all derived results, or only the filtered hits). transferAll is really a "force full-transfer" override, so enableGpuFilter: true, transferAll: false is the normal compact-filtering case — filter on, send back only the hits. How they combine:
enableGpuFilter |
transferAll |
Behaviour |
|---|---|---|
true |
false |
Compact mode — filter on the GPU, only the matching candidates are transferred back (the recommended, fastest setup). |
true |
true |
Filter is uploaded but overridden to full transfer — every derived result is sent back (e.g. vanity scanning, or a device without OpenCL 2.0+ compact support). |
false |
(any) | No GPU filter — full transfer (legacy); the consumer checks every result against addressLookupBackend on the CPU. |
Compact mode runs only when a filter was uploaded and transferAll is false (OpenCLContext computes transferAll = producerOpenCL.transferAll || !gpuFilterUploaded).
How compact mode works — the kernel checks both the compressed and uncompressed hash160 of each candidate against the uploaded filter; a work-item that hits claims an output slot via an atomic_add on a shared count word and writes its entry there, so the buffer holds exactly the hits (each entry carries its own work-item index for secret-key reconstruction). A non-hit writes nothing.
No false negatives — the Binary Fuse 8 guarantee carries to the GPU unchanged: any address actually in the database is always flagged. The GPU pre-filter only shrinks the GPU→CPU transfer; the ~0.4 % false positives among the survivors are verified against LMDB on the CPU exactly as for the in-RAM BINARY_FUSE_8 backend (LMDB stays open as the verifier), so no false positive is ever reported as a hit.
Device requirements — compact mode needs an OpenCL 2.0+ device (the atomic_add on global memory) and a little-endian device (the kernel canonicalises its output as little-endian). Both are checked at producer init and fail fast with a clear message; on an older device set transferAll: true to keep the full-transfer path. The GPU/CPU lookup parity (key extraction + hash formula) is pinned by Fuse8GpuHashParityTest and exercised end-to-end on a real OpenCL device by OpenCLCompactOutputIntegrationTest.
Measured speedup — see GPU Binary Fuse 8 filter — compact output vs. full transfer under Performance Benchmarks for an A/B measurement (~2.2× at grid 19 on an RTX 3070).
When addressLookupBackend is BLOOM, the false positive probability is configurable via bloomFilterFpp:
"addressLookupBackend": "BLOOM",
"bloomFilterFpp": 0.1bloomFilterFpp |
Behaviour |
|---|---|
0.01 |
✅ Only ~1% false positives — high accuracy, more memory |
0.05 |
⚖️ Balanced tradeoff between memory and performance |
0.1 – 0.2 |
🪶 Very memory-efficient — suitable if some false positives are acceptable |
Memory cost (Bloom only — HASHSET and TRUNCATED_LONG_64 follow the RAM cost column of the backend table above):
bloomFilterFpp |
Light Database (~132M) | Full Database (~1.37B) |
|---|---|---|
0.1 |
~80 MB | ~800 MB |
0.05 |
~100 MB | ~1024 MB |
0.01 |
~151 MB | ~1574 MB |
The OpenCL kernel performs blazing fast public key hashing directly on the GPU using:
SHA-256(pubkey.x || pubkey.y)followed byRIPEMD-160(SHA-256(pubkey))
This allows each GPU thread to independently generate full Bitcoin-style public key hashes (hash160) without CPU involvement.
Benefits:
- No host-side post-processing needed
- Fully parallelized and memory-efficient
- Ideal for massive batch generation and filtering
✅ Output is already in hash160 format, ready for address comparison
The addresses are stored in a high-performance database: LMDB. The database can be used to check whether a generated address has ever been used.
The importer supports reading multiple .txt or .text files, each containing one address per line in arbitrary order. Lines may vary in address type and format. Each line may also optionally include an associated coin amount.
-
P2PKH – Pay to Public Key Hash Encoded using Base58. Most commonly used format for legacy Bitcoin addresses.
-
P2SH – Pay to Script Hash Base58-encoded address type used for multisig and other script-based spending conditions.
-
P2WPKH – Pay to Witness Public Key Hash Native SegWit v0 address, encoded in Bech32. One private key deterministically produces one address.
-
P2MS – Pay to Multisig Custom format prefixed with
d-,m-, ors-(e.g.m-<script>). ❌ Not Supported: Script-based format. Private keys cannot deterministically generate P2MS addresses without knowing the script. -
P2WSH – Pay to Witness Script Hash Native SegWit v0 address for scripts, encoded in Bech32. ❌ Not Supported: Script Hash format. Addresses depend on the script hash, not directly on the public key.
-
P2TR – Pay to Taproot Native SegWit v1 (Taproot) address, encoded using Bech32m. ❌ Not Supported: Requires key tweaking based on an optional script. Without knowing the script, you cannot generate matching P2TR addresses from a private key. One private key can create infinite different P2TR addresses. This breaks the tool's design, which maps private keys to deterministic addresses.
Note on Witness Versions: Bech32 was introduced in BIP-173 for SegWit v0 (P2WPKH, P2WSH). Bech32m, defined in BIP-350, is used for SegWit v1 (P2TR).
In addition to standard script types, the importer also recognizes and supports several special address encodings:
-
Bitcoin Cash (Base58 prefix
q)
Legacy Bitcoin Cash addresses using theq-prefix are automatically converted to legacy Bitcoin format. -
BitCore WKH
Custom format prefixed withwkh_and encoded in Base36. -
Riecoin P2SH as ScriptPubKey
Riecoin P2SH addresses are provided as raw ScriptPubKey hex (starting with76a914...).
Legend:
✅ = Supported and tested
❌ = Explicitly not supported (format known but intentionally excluded)
empty = Unknown or unverified — either not implemented by the altcoin project, or no valid address example was found (thus no implementation or test exists)
| Coin | TAG | P2PKH | P2SH | P2WPKH | P2WSH | P2MS | P2TR |
|---|---|---|---|---|---|---|---|
| 42-coin | 42 | ✅ | ✅ | ||||
| Alias | ALIAS | ✅ | |||||
| Argoneum | AGM | ✅ | |||||
| Artbyte | ABY | ✅ | |||||
| Auroracoin | AUR | ✅ | |||||
| B3Coin | B3 | ✅ | |||||
| BBQCoin | BQC | ✅ | |||||
| Bean Cash | BEAN | ✅ | |||||
| Biblepay | BBP | ✅ | |||||
| BitBay | BAY | ✅ | |||||
| BitBlocks | BBK | ✅ | |||||
| Bitcoin | BTC | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Bitcoin Cash | BCH | ✅ | ❌ | ❌ | |||
| Bitcoin Gold | BTG | ✅ | |||||
| Bitcoin Oil | BTCO | ✅ | ✅ | ❌ | |||
| Bitcoin Plus | XBC | ✅ | |||||
| BitCore | BTX | ✅ | ✅ | ❌ | |||
| Bitmark | BTMK | ✅ | |||||
| BlackCoin | BLK | ✅ | |||||
| BlakeBitcoin | BBTC | ✅ | |||||
| Blakecoin | BLC | ✅ | |||||
| Blocknet | BLOCK | ✅ | |||||
| BolivarCoin | BOLI | ✅ | |||||
| BYTZ | BYTZ | ✅ | ✅ | ||||
| Canada-eCoin | CDN | ✅ | ✅ | ❌ | |||
| Catcoin | CAT | ✅ | |||||
| ChessCoin 0.32% | CHESS | ✅ | |||||
| Clam | CLAM | ✅ | |||||
| CloakCoin | CLOAK | ✅ | ✅ | ||||
| C-Note | CNOTE | ✅ | |||||
| Coino | CNO | ✅ | |||||
| ColossusXT | COLX | ✅ | |||||
| Compound | COMP | ✅ | |||||
| CROWN | CRW | ✅ | |||||
| Cryptoshares | SHARES | ✅ | |||||
| Curecoin | CURE | ✅ | |||||
| Dash | DASH | ✅ | ✅ | ||||
| Defcon | DEFCON | ✅ | ✅ | ||||
| DeFiChain | DFI | ✅ | ✅ | ✅ | ❌ | ||
| Deutsche eMark | DEM | ✅ | |||||
| Diamond | DMD | ✅ | |||||
| DiminutiveCoin | DIMI | ✅ | |||||
| DigiByte | DGB | ✅ | ✅ | ❌ | |||
| DigitalCoin | DGC | ✅ | |||||
| Dimecoin | DIME | ✅ | |||||
| Divicoin | DIVI | ✅ | |||||
| Dogecoin | DOGE | ✅ | ✅ | ||||
| Dogmcoin | DOGM | ✅ | |||||
| Doichain | DOI | ✅ | ✅ | ❌ | |||
| e-Gulden | EFL | ✅ | |||||
| Electron | ELT | ✅ | |||||
| Element (HYP) | HYP | ✅ | |||||
| Elite | 1337 | ✅ | |||||
| Emerald | EMD | ✅ | |||||
| EverGreenCoin | EGC | ✅ | |||||
| Feathercoin | FTC | ✅ | ✅ | ❌ | |||
| Firo | FIRO | ✅ | |||||
| Freedomcoin | FREED | ✅ | |||||
| GapCoin | GAP | ✅ | |||||
| Goldcash | GOLD | ✅ | |||||
| GoldCoin | GLC | ✅ | |||||
| Groestlcoin | GRS | ✅ | ✅ | ❌ | |||
| Hemis | HMS | ✅ | |||||
| Herencia | HEIRS | ✅ | |||||
| HoboNickels | HBN | ✅ | |||||
| HTMLCOIN | HTML | ✅ | |||||
| I/O Coin | IOC | ✅ | |||||
| IDChain | DCT | ✅ | |||||
| Innova | INN | ✅ | |||||
| InfiniLooP | IL8P | ✅ | |||||
| Infinitecoin | IFC | ✅ | |||||
| iXcoin | IXC | ✅ | |||||
| Kobocoin | KOBO | ✅ | |||||
| Komodo | KMD | ✅ | |||||
| Lanacoin | LANA | ✅ | |||||
| Litecoin | LTC | ✅ | ✅ | ✅ | ❌ | ||
| Litecoin Cash | LCC | ✅ | ✅ | ❌ | |||
| LiteDoge | LDOGE | ✅ | |||||
| Lithium | LIT | ✅ | |||||
| Luckycoin | LKY | ✅ | |||||
| Lynx | LYNX | ✅ | |||||
| MasterNoder2 | MN2 | ✅ | |||||
| Mooncoin | MOON | ✅ | ✅ | ✅ | ❌ | ||
| MotaCoin | MOTA | ✅ | |||||
| Myriad | XMY | ✅ | ✅ | ✅ | ❌ | ||
| Namecoin | NMC | ✅ | ✅ | ❌ | |||
| NewYorkCoin | NYC | ✅ | |||||
| Novacoin | NVC | ✅ | |||||
| Oduwacoin | OWC | ✅ | |||||
| PAC Protocol | PAC | ✅ | |||||
| PakCoin | PAK | ✅ | |||||
| PandaCoin | PND | ✅ | |||||
| Particl | PART | ✅ | ✅ | ||||
| PeepCoin | PCN | ✅ | |||||
| Peercoin | PPC | ✅ | |||||
| Photon | PHO | ✅ | |||||
| Pinkcoin | PINK | ✅ | |||||
| PIVX | PIVX | ✅ | |||||
| PotCoin | POT | ✅ | |||||
| Primecoin | XPM | ✅ | |||||
| PutinCoin v2 | PUT | ✅ | |||||
| Quark | QRK | ✅ | |||||
| Raptoreum Core | RTM | ✅ | |||||
| Reddcoin | RDD | ✅ | |||||
| Riecoin | RIC | ✅ | ✅ | ❌ | |||
| SaluS | SLS | ✅ | ✅ | ||||
| SexCoin | SXC | ✅ | |||||
| Slimcoin | SLM | ✅ | |||||
| Smileycoin | SMLY | ✅ | |||||
| Smoke | SMOKE | ✅ | |||||
| SpaceXpanse | ROD | ✅ | ✅ | ❌ | |||
| Sparks | SPK | ✅ | |||||
| Stakecoin | STK | ✅ | |||||
| Sterlingcoin | SLG | ✅ | |||||
| Stronghands | SHND | ✅ | |||||
| Syscoin | SYS | ✅ | ✅ | ❌ | |||
| TajCoin | TAJ | ✅ | |||||
| Terracoin | TRC | ✅ | |||||
| TheHolyRogerCoin | ROGER | ✅ | ✅ | ❌ | |||
| Trezarcoin | TZC | ✅ | |||||
| Trollcoin | TROLL | ✅ | |||||
| UFO | UFO | ✅ | ✅ | ✅ | ❌ | ||
| UFOhub | UFOHUB | ✅ | |||||
| Unitus | UIS | ✅ | |||||
| UniversalMolecule | UMO | ✅ | |||||
| Unobtanium | UNO | ✅ | |||||
| Validity | VAL | ✅ | |||||
| Vanillacash | XVC | ✅ | |||||
| VeriCoin | VRC | ✅ | |||||
| Versacoin | VCN | ✅ | |||||
| Vertcoin | VTC | ✅ | ✅ | ❌ | |||
| VirtaCoin | VTA | ✅ | |||||
| VirtacoinPlus | XVP | ✅ | |||||
| WorldCoin | WDC | ✅ | |||||
| Zetacoin | ZET | ✅ | |||||
| ZCash | ZEC | ✅ | ✅ |
Useful txt/text file provider:
Import one or more plaintext address files into an LMDB database with the AddressFilesToLMDB command
(see examples/config_AddressFilesToLMDB.json and the
run_AddressFilesToLMDB launchers). Files are read one at a time; a single buffered reader streams
their lines to threads parser workers, and one writer stores the parsed addresses in LMDB in
batches. The performance-relevant settings on addressFilesToLMDB:
| Setting | Default | Purpose |
|---|---|---|
threads |
1 |
Parser threads. 1 keeps the exact deterministic import order; ≥2 parses in parallel — order-independent when useStaticAmount = true, otherwise a warning is logged. |
writeBatchSize |
32768 |
Addresses written per LMDB transaction. |
queueCapacity |
200000 |
Capacity of each internal hand-off queue (back-pressure vs. memory). |
The single LMDB writer is the throughput ceiling (LMDB permits one writer at a time), so a handful of parser threads is enough to keep it fed.
Optional — compact the finished database. After the import you can write a smaller, read-denser
copy with the CompactLMDB command (LMDB MDB_CP_COMPACT; see
examples/config_CompactLMDB.json). It opens the source
read-only, writes data.mdb into a separate target directory, and verifies the copy's entry count
against the source. The read benefit is largest when the database exceeds RAM (a smaller file raises
the OS page-cache hit rate); for a database that fits in RAM the gain is marginal.
Optional — extract what a newer database adds over a reference. The LMDBDelta command
(see examples/config_LMDBDelta.json and the run_LMDBDelta
launchers) diffs one or more lmdbConfigurationReadOnlyList databases against a single
referenceLmdbConfigurationReadOnly and writes every address present in the others but not in the
reference to deltaAddressesFile — one mainnet Base58 1… address per line, ready to be re-imported
with AddressFilesToLMDB. Because LMDB stores keys sorted, all databases are streamed together as a
k-way cursor merge: memory stays flat regardless of the delta size (only one 20-byte key per cursor is
held), cross-database duplicates are removed, and the whole pass is sequential I/O rather than random
lookups. The re-encoding keys on the 20-byte hash160, so the round trip is lossless for lookups even
though a hash that originally came from a P2SH/bech32/altcoin address is written in 1… form.
The exporter provides various output formats for Bitcoin and altcoin address data:
-
HexHash
Exports addresses as rawhash160(RIPEMD-160 of the SHA-256 of the public key), encoded in hexadecimal. No version byte or checksum is included. Ideal for advanced usage and low-level comparison. Best viewed in fixed-width hex viewers (e.g., HxD). -
FixedWidthBase58BitcoinAddress
Exports Base58Check-encoded addresses (e.g., legacyP2PKH) in a fixed-width format for consistent alignment. No amount is included. Suitable for hex/byte-aligned visual inspection and batch comparison. -
DynamicWidthBase58BitcoinAddressWithAmount
Exports Base58Check-encoded addresses along with their associated amounts (e.g., balance or UTXO value), using a dynamic-width format. Suitable for human-readable CSV-like formats and analytics.
I am in the process of building and publishing databases containing large sets of Bitcoin and altcoin addresses.
(Refer to the Import section above for details on supported address formats.)
The sources of these addresses are confidential, but you are fully permitted to extract, inspect, and use them.
You are also welcome to extend this database by importing your own address data. This allows you to build upon the existing dataset, tailoring it to your specific needs (e.g., adding additional coins, formats, or private address collections).
If you're missing any information or have questions about usage or content, feel free to ask or open an issue.
- Light (5.4 GiB), Last update: July 20, 2026
- Contains Bitcoin addresses whith amount and many altcoin addresses with amount.
- Static amount of 0 is used to allow best compression.
- Unique entries: 141045995
- Mapsize: 5942 MiB
- Link (3.90 GiB zip archive): http://ladenthin.net/lmdb_light.zip
- Link extracted addresses as txt (5.51 GiB) (2.44 GiB zip archive); open with HxD, set 42 bytes each line: http://ladenthin.net/LMDBToAddressFile_Light_HexHash.zip
💡 Hint: For the light database the recommended lookup backend is
BINARY_FUSE_8:"addressLookupBackend" : "BINARY_FUSE_8"See Pluggable Address Lookup Backends above for the full trade-off matrix and benchmark numbers.
BINARY_FUSE_8is a tiny in-RAM filter (~149 MB for the Light DB, ~0.4% false-positive rate, no false negatives) in front of LMDB: it answers almost every miss straight from RAM and keeps LMDB open only to verify the ~0.4% of filter hits, so a false positive is never reported as a hit. If you run on a GPU, pair it with theFUSE_16GPU pre-filter (producerOpenCL.gpuFilterType) — the two form an independent cascade (see below). Want lower fallthrough for a CPU-only run?BINARY_FUSE_16(~298 MB, ~0.0015% FPR) verifies against LMDB far less often. If you need LMDB fully closed (a self-contained in-RAM snapshot) and can spare the memory,HASHSET(~10 GB) is exact. AvoidTRUNCATED_LONG_64here: at the Light DB's 141 M entries its lookup latency is the worst of all backends while still costing ~7× the RAM ofBINARY_FUSE_8.
Checksums lmdb_light.zip
lmdb_light.zip CRC32 85D5B66B
lmdb_light.zip MD5 85D99D42307FAE3D2DE9842E1BC2E05C
lmdb_light.zip RipeMD160 63F9A232B474FBDBE0BF44424848204F96D815F9
lmdb_light.zip SHA-1 6C70EB6C9DA6E856F6AA37DE8005CDE63C73C40E
lmdb_light.zip SHA-256 8CF75775992A3CF6DF7E92BE8BF11512865B64355AE8B9B4AC4AA17EA0D23099
lmdb_light.zip SHA-512 7F5DC94BECA62F75ECDC3B2982F2E049B65BA1999A565184EC037F86455EE3A84A7CFF2CB5417EB897D98784BF5928376CD328E4428AEB53B546B73737DA707B
lmdb_light.zip SHA3-224 7828FF892A2C3A132A31097B3E474960B9724F11D89E417CC41C262F
lmdb_light.zip SHA3-256 7EAF32276D0E04199D10694DD204FBC2FD35594E537A2CFA5C5BCA6DEF3B19FB
lmdb_light.zip SHA3-384 7DEE61D19C04734AEBD808AFF2D9B3AD14A0659D3CDB00E4E6E4324CA7317F1C48A6B32E7D02DBE0E68A67F843F2018D
lmdb_light.zip SHA3-512 FD9F17E3ECAD1896A984BE5FA2B884184608DF2CDAA35E7465EE4DD5735FDD2E77F361EDFA563AC8754965375C68B140208C8FEFCDE18932897B929CC707BC2EChecksums LMDBToAddressFile_Light_HexHash.zip
LMDBToAddressFile_Light_HexHash.zip CRC32 2F79BEB6
LMDBToAddressFile_Light_HexHash.zip MD5 42760355C2EFBCD964152F333D49083D
LMDBToAddressFile_Light_HexHash.zip RipeMD160 6E089BC89F87F6FE9D928CA7A82CB8276E76196D
LMDBToAddressFile_Light_HexHash.zip SHA-1 872D5EF8A90EB7B0EEC44A4D7A0284D886CAE3D5
LMDBToAddressFile_Light_HexHash.zip SHA-256 9079152EB750D640E469A1C050FA27D982CFC2C08F519922468061834776759E
LMDBToAddressFile_Light_HexHash.zip SHA-512 9A5298A8391F317B4593E36EB5111CAC6FF3BC23582C8A15A1052D2EE95EFA2A9F590C1BD6F28234AD1B6163BB98CD62939928F9DC20C3AF4D320D18B332987C
LMDBToAddressFile_Light_HexHash.zip SHA3-224 45B3DED0DEFCEB7A828FDB8D7A67FA86456C8369A191B0E2E4C5A18C
LMDBToAddressFile_Light_HexHash.zip SHA3-256 7CCCD74E911B030DBD84691E6C5253EC460B10ECC1F9327CB985276FFC36AC30
LMDBToAddressFile_Light_HexHash.zip SHA3-384 CDEE253DF906C04FC03C4B725A123FF01AF3AAF251C4633008D3D67F5D7D567703E82F093FA085014D4A36C48EEA604F
LMDBToAddressFile_Light_HexHash.zip SHA3-512 1C66472C3605FCB6833163B9FBDF81A6581F012A1FE0D6236C4804909AFD6E0CF15DB11FD53EFD507C8AF7CEDFA0F06EDD03FE2F84C7ACD38058604FF304EF66- Full (64.0 GiB), Last update: July 20, 2026
- Contains all Bitcoin addresses which are ever used and many altcoin addresses with and without amount.
- Static amount of 0 is used to allow best compression.
- Unique entries: 1472947953
- Mapsize: 58368 MiB
- Link (41.6 GiB zip archive): http://ladenthin.net/lmdb_full.zip
- Link extracted addresses as txt (25 GiB zip archive); open with HxD, set 42 bytes each line: http://ladenthin.net/LMDBToAddressFile_Full_HexHash.zip
⚠️ Backend choice for the full database. At 1.377 B entries,HASHSETrequires roughly ~110 GB of RAM — impractical on almost any consumer hardware. Pick one of the realistic options instead:"addressLookupBackend" : "BINARY_FUSE_8"
Choice RAM needed at Full DB LMDB stays open? When to pick BINARY_FUSE_8~1.5 GB yes recommended — tiny filter in front of LMDB; keeps LMDB open to verify the 0.4% of filter hits; also pairs with the FUSE_16GPU pre-filterBINARY_FUSE_16~3.1 GB yes recommended for CPU-only cold-storage runs — 0.0015% FPR means far fewer LMDB verifications BLOOM(FPP 0.01)~1.6 GB yes keeps LMDB open as the verifier; skips it for the overwhelming majority of misses LMDB_ONLYminimal (mmap only) yes fallback when there is essentially no spare RAM and disk-backed mmap is acceptable TRUNCATED_LONG_64~11 GB no not recommended — worst lookup latency at this size (see backend section); use only if you specifically need LMDB fully closed and can spare the RAM Do not select
HASHSETfor the full database. See Pluggable Address Lookup Backends above for the full trade-off matrix and per-tier memory footprint.
Checksums lmdb_full.zip
lmdb_full.zip CRC32 731194D3
lmdb_full.zip MD5 C7A144E1474E537B58DE91D8FE9BA762
lmdb_full.zip RipeMD160 ED6964A1722BB453D1CA97380BEEE3CA9DC495E5
lmdb_full.zip SHA-1 8EFCE18E7173AD2ABE8D0046D5E703A337939E1E
lmdb_full.zip SHA-256 0CDF5F126BD090A18423B3AE47B34DC84506E88A5F882DF364BEFC3F68025EB0
lmdb_full.zip SHA-512 402BBD2BD8ADAF7837851DB8C9E9C2B9D7C8F54FCB29F4C9E7F9A021D12988D36069549CE586D015B53011E5E00DFD9EAAB9506A0659C21F8DB4C1F66A0F000D
lmdb_full.zip SHA3-224 BF2B576F66109EBD4F0C1A67E68EAC567E106186949F9E4E5B10DCBB
lmdb_full.zip SHA3-256 F2A64AD5F5241B30FE4C6C4DDE5DC8F4151F0BEA00AB9A015F2B5A2E90717BAD
lmdb_full.zip SHA3-384 4D0F0E86610FBF6720AA47E3E0A55C0276AE8A7C44CAFB9B67117314518FB17248951F855DDA324CB40232CE9832B335
lmdb_full.zip SHA3-512 EA14474708CDEF4A2422961077F066FB853247734CF1D782C5AC66F5CFCBADAB6AC32B047575501ED22490912AE6E4DA1C77BE79EF1965B0D77982F5ABE54B7EChecksums LMDBToAddressFile_Full_HexHash.zip
LMDBToAddressFile_Full_HexHash.zip CRC32 B2FB544F
LMDBToAddressFile_Full_HexHash.zip MD5 439B5202737C1B5D8766E3096191411B
LMDBToAddressFile_Full_HexHash.zip RipeMD160 77DED76F7C9CFFB103CD94B129FE9DEF0A5445F0
LMDBToAddressFile_Full_HexHash.zip SHA-1 581067DB3C986691CEE90A315BB3668FB2643F0A
LMDBToAddressFile_Full_HexHash.zip SHA-256 A4AAE52539F457B8551AF8E29324297029D7A3F760F185C028DB1D741E458585
LMDBToAddressFile_Full_HexHash.zip SHA-512 090E3289BBFBAEB8F68C6EBBCE0C03843BAE389D4D4EBD178881EF11AB39C8035A726974003A01B83F15A839F0C48131353E92266F24E625F534288D2AE2682A
LMDBToAddressFile_Full_HexHash.zip SHA3-224 C4775299678F61E000F7AE11EDB0BBF1FF142797BBA31BC95E1290D2
LMDBToAddressFile_Full_HexHash.zip SHA3-256 5C563C323850638905FC1DBBB186FABF47F6EBF473ED50C5C5852A63EB9B9B3B
LMDBToAddressFile_Full_HexHash.zip SHA3-384 B7561CAF76447F75813DDDE9D07D85CE5FDA8E7207C36066F79B5D105340A63AC42A22BEAF1AF9D01146202B404310CD
LMDBToAddressFile_Full_HexHash.zip SHA3-512 6ED622047AF04BBECF46BC2E50E9169ADC2DD0B7D35E58F6A4F7B33D64AB4C1C8F91CACF89F12A87C7AD5BAC2974BFA41100550E8A8D4A2298C3A5FFB012B8AB- https://github.com/Pymmdrza/Rich-Address-Wallet
- https://github.com/mycroft/chainstate
- https://github.com/graymauser/btcposbal2csv
- https://blockchair.com/dumps
- https://balances.crypto-nerdz.org/
BitcoinAddressFinder supports multiple pseudorandom number generators (PRNGs) for private-key creation.
Pick one in your JSON via the randomAlgorithm field, e.g. "randomAlgorithm": "SECURE_RANDOM".
This flexibility lets you switch between production-grade entropy and deterministic or deliberately weak sources for audits and research.
- 🔐 Secure wallet generation
- 🧪 Testing deterministic output
- 🕵️ Simulating vulnerabilities
- 🔄 Reproducible scans
⚠️ Security Warning: This software is intended for research and educational purposes. Do not use it in production or on systems connected to the internet.
A secure environment should be fully isolated — ideally an air-gapped computer (physically disconnected from all networks).
The software is highly optimized for performance and not designed to run in constant time, which means it may be vulnerable to side-channel attacks on shared or exposed systems.
For generated vanity addresses or private keys, consider storing them safely using a paper wallet.
| Value | Description |
|---|---|
SECURE_RANDOM |
✅ Cryptographically secure system CSPRNG (/dev/urandom, Windows CNG). Recommended for real wallet generation. |
RANDOM_CURRENT_TIME_MILLIS_SEED |
java.util.Random seeded with the current timestamp. Insecure; handy for replaying time-window RNG flaws. |
RANDOM_CUSTOM_SEED |
java.util.Random with user-supplied seed. Fully deterministic; useful for reproducible fuzzing or fixed keyspaces. |
SHA1_PRNG |
Best choice for real wallet generation – uses system CSPRNG (e.g. /dev/urandom, Windows CNG).
Recreates time-based vulnerabilities using java.util.Random seeded with the current system time.
// ...
"keyProducerJavaRandom": [
{
"keyProducerId": "exampleKeyProducerId",
"randomAlgorithm": "RANDOM_CURRENT_TIME_MILLIS_SEED"
}
],
// ...Fully deterministic PRNG using java.util.Random with a user-defined seed. Useful for reproducible scans and testing.
Fully deterministic output. Useful for reproducible tests or fixed keyspace scans. Without explicit seed:
// ...
"keyProducerJavaRandom": [
{
"keyProducerId": "exampleKeyProducerId",
"randomAlgorithm": "RANDOM_CUSTOM_SEED"
}
],
// ...With custom deterministic seed:
// ...
"keyProducerJavaRandom": [
{
"keyProducerId": "exampleKeyProducerId",
"randomAlgorithm": "RANDOM_CUSTOM_SEED",
"customSeed": 123456789
}
],
// ...Legacy deterministic PRNG using "SHA1PRNG". Used to reproduce the 2013 Android SecureRandom vulnerability. Can be used with or without an explicit seed.
Simulates old Android bug. May produce the same keys if seeded poorly or not at all. Without seed:
// ...
"keyProducerJavaRandom": [
{
"keyProducerId": "exampleKeyProducerId",
"randomAlgorithm": "SHA1_PRNG"
}
],
// ...With custom seed:
// ...
"keyProducerJavaRandom": [
{
"keyProducerId": "exampleKeyProducerId",
"randomAlgorithm": "SHA1_PRNG",
"customSeed": 987654321
}
],
// ...HD-wallet-style derivation: mnemonic + passphrase → BIP32/BIP44 keys.
Hierarchical deterministic key generator using a BIP39 mnemonic and optional passphrase. Allows full BIP32/BIP44 path derivation and reproducible HD wallets.
| JSON field | Type | Default | Purpose |
|---|---|---|---|
mnemonic |
string | — | 12/24-word BIP39 sentence |
passphrase |
string | "" |
Optional BIP39 salt (“wallet password”) |
hardened |
boolean | false |
Whether to use hardened key derivation (adds 0x80000000 to indices) |
bip32Path |
string | "M/44H/0H/0H/0" (constant DEFAULT_BIP32_PATH) |
Base derivation path; must start with M/ |
creationTimeSeconds |
number | 0 |
Epoch-seconds creation timestamp; fed to DeterministicSeed.ofMnemonic |
Minimal:
// ...
"keyProducerJavaBip39": [
{
"keyProducerId": "exampleKeyProducerId",
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
}
],
// ...Full:
// ...
"keyProducerJavaBip39": [
{
"keyProducerId": "exampleKeyProducerId",
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"passphrase": "correct horse battery staple",
"hardened" : false,
"bip32Path": "M/44H/0H/0H/0",
"creationTimeSeconds": 1650000000
}
],
// ...🧪 Runnable example:
examples/config_Find_1CPUProducerBip39.jsonis a complete single-CPU-producer + single-consumerFindconfig that walks the BIP39 derivation sequence (batchUsePrivateKeyIncrement: false). Note: withbatchUsePrivateKeyIncrement: trueonly the first derived key is used as an LSB-increment base (see the batch-mode limitation).
This mode generates private keys sequentially in batches within a specified key range. It is especially useful for:
- Systematic scanning of a defined keyspace range (e.g., for key recovery or cryptographic research)
- Batch processing optimized for GPU/OpenCL parallelization
- Ensuring deterministic and reproducible key generation from start to end
| JSON field | Type | Default | Purpose |
|---|---|---|---|
startPrivateKey |
string | 0000000000000000000000000000000000000000000000000000000000000002 |
Hex string of the first private key in the range (inclusive) |
endPrivateKey |
string | FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 (secp256k1 group order) |
Hex string of the last private key in the range (inclusive) |
- Scanning begins at
startPrivateKey, producing sequential private keys in batches of2^batchSizeInBitskeys each, up to and includingendPrivateKey. See Batch Size per Kernel (batchSizeInBits) above for the exponent → batch-size mapping and typical values. - Each batch contains exactly
2^batchSizeInBitskeys. - If the next full batch would go beyond
endPrivateKey, the process stops with an exception. - The
endPrivateKeyis inclusive, but partial batches are not allowed by default — batches must fit completely inside the range. - For optimal performance, especially with OpenCL, configure
batchSizeInBits(and the matchingkeysPerWorkItem) so that batches align perfectly within the range. This prevents errors and maximizes throughput.
Note: If unsure, set the
endPrivateKeyslightly higher to match a2^batchSizeInBitsmultiple. This helps avoid exceptions and ensures efficient scanning.
Example minimal configuration:
// ...
{
"keyProducerJavaIncremental": [
{
"keyProducerId": "exampleKeyProducerId",
"startPrivateKey": "0000000000000000000000000000000000000000000000000000000000000002",
"endPrivateKey": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
}
]
}
// ...🧩 This configuration incrementally searches a defined range of private keys. It is particularly suited for brute-force challenges, such as the 71st Bitcoin puzzle transaction.
The private key range is specified by two 64-character hex strings: startPrivateKey and endPrivateKey.
// ...
"keyProducerJavaIncremental": [
{
"keyProducerId": "exampleKeyProducerJavaIncremental",
"startPrivateKey": "0000000000000000000000000000000000000000000000400000000000000000",
"endPrivateKey": "00000000000000000000000000000000000000000000007fffffffffffffffff"
}
],
// ...Read raw private keys from a TCP socket stream (client or server mode).
Useful for piping externally generated secrets (e.g., from Python, Go, etc.) directly into the finder.
| JSON Field | Type | Default | Description |
|---|---|---|---|
keyProducerId |
string | — | Unique identifier for this key producer |
mode |
string enum (CLIENT, SERVER) |
"SERVER" |
Whether to connect to a remote socket (CLIENT) or wait for connections (SERVER) |
host |
string | "localhost" |
Remote host to connect to (used only in CLIENT mode) |
port |
number | 12345 |
TCP port to connect to or bind on |
timeoutMillis |
number | 3000 |
Socket timeout in milliseconds, applied to accept (server), connect (client) and per-read. Must be non-negative |
logReceivedSecret |
boolean | false |
Whether to log each received private key as a hex string |
connectRetryCount |
number | 5 |
Number of times to retry establishing a socket connection (client mode) |
connectRetryDelayMillis |
number | 1000 |
Delay between connection retry attempts (in milliseconds) |
maxWorkSize |
number | 16777216 |
Maximum number of secrets to request in a single call |
Minimal:
// ...
"keyProducerJavaSocket": [
{
"keyProducerId": "exampleKeyProducerId",
"host": "localhost",
"port": 12345,
"mode": "SERVER"
}
],
// ...Full:
// ...
"keyProducerJavaSocket": [
{
"keyProducerId": "exampleKeyProducerId",
"host": "localhost",
"port": 12345,
"mode": "SERVER",
"timeoutMillis": 3000,
"logReceivedSecret": true,
"connectRetryCount": 5,
"connectRetryDelayMillis": 1000,
"maxWorkSize": 16777216
}
],
// ...import socket
import os
import time
import binascii
HOST = 'localhost'
PORT = 12345
while True:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
print(f"Connected to {HOST}:{PORT}")
while True:
private_key = os.urandom(32)
print("Sending key:", binascii.hexlify(private_key).decode())
s.sendall(private_key)
time.sleep(1.0) # Slight delay to avoid flooding
except ConnectionRefusedError:
print("Connection refused, retrying in 1 second...")
time.sleep(1)
except BrokenPipeError:
print("Connection lost, reconnecting...")
time.sleep(1)- Each private key must be exactly 32 raw bytes, sent binary, with no delimiter or framing.
- Keys are interpreted as big-endian (new BigInteger(1, bytes) in Java).
- Sending fewer than 32 bytes will cause blocking or exceptions.
Receive raw private keys via a ZeroMQ PULL socket.
Ideal for decoupled key streaming where producers push keys using ZeroMQ PUSH sockets.
| JSON field | Type | Default | Description |
|---|---|---|---|
| keyProducerId | string | — | Unique identifier for this key producer |
| address | string | tcp://localhost:5555 | The ZeroMQ address to bind or connect to |
| mode | string enum (BIND, CONNECT) | BIND | Whether this socket binds to or connects |
| timeoutMillis | number | -1 |
Receive timeout in milliseconds:0 = non-blocking,-1 = infinite,>0 = wait that long |
| logReceivedSecret | boolean | false | Whether to log each received secret in hex |
Minimal:
// ...
"keyProducerJavaZmq": [
{
"keyProducerId": "exampleKeyProducerId",
"address": "tcp://localhost:5555"
}
],
// ...Full:
// ...
"keyProducerJavaZmq": [
{
"keyProducerId": "exampleKeyProducerId",
"address": "tcp://localhost:5555",
"mode": "BIND",
"timeoutMillis": -1,
"logReceivedSecret": true
}
],
// ...import zmq
import os
import time
import binascii
from enum import Enum
class Mode(Enum):
CONNECT = "connect"
BIND = "bind"
ADDRESS = "tcp://localhost:5555"
MODE = Mode.CONNECT # Change to Mode.BIND to switch
context = zmq.Context()
while True:
try:
socket = context.socket(zmq.PUSH)
socket.setsockopt(zmq.SNDHWM, 1) # Limit send queue to 1 message
if MODE == Mode.BIND:
socket.bind(ADDRESS)
print(f"Bound to {ADDRESS}")
else:
socket.connect(ADDRESS)
print(f"Connected to {ADDRESS}")
while True:
private_key = os.urandom(32)
print("Sending key:", binascii.hexlify(private_key).decode())
try:
socket.send(private_key, flags=zmq.NOBLOCK)
except zmq.Again:
print("Send queue full, message not sent.")
time.sleep(1.0) # Slight delay to avoid flooding
except zmq.ZMQError as e:
print(f"ZMQ error occurred: {e}, retrying in 1 second...")
time.sleep(1)
finally:
try:
socket.close()
except:
pass- Each private key must be exactly 32 raw bytes, sent as a single ZeroMQ message.
- Messages are interpreted as unsigned big-endian (
new BigInteger(1, bytes)in Java). - No framing or delimiters needed—ZeroMQ handles message boundaries.
- Messages larger or smaller than 32 bytes will be discarded or raise errors.
Read pre-generated secrets from one or more text files instead of generating them at runtime.
Unlike the entries above (which are key producers nested in a producer), this is a top-level
producer placed directly under finder.producerJavaSecretsFiles. One secret per line; the line
format is selected with secretFormat. See config_Find_SecretsFile.json.
| JSON field | Type | Default | Description |
|---|---|---|---|
keyProducerId |
string | — | Unique identifier for this producer |
secretsFiles |
array of string | [] |
Paths of the files to read (relative to the working dir) |
secretFormat |
string enum | STRING_SHA256 |
Line format of every file (see below) |
Supported secretFormat values:
| Value | Meaning |
|---|---|
BIG_INTEGER |
Decimal big integer per line (the raw private key as a base-10 number) |
DUMPED_PRIVATE_KEY |
WiF (Wallet Import Format), e.g. 5K2YUVmWfxbmvsNxCsfvArXdGXm7d5DC9pn4yD75k2UaSYgkXTh |
SHA256 |
Hex-encoded SHA-256 digest used directly as the private key |
STRING_SHA256 |
Brainwallet: the line is hashed with SHA-256 and that digest is the private key |
// ...
"producerJavaSecretsFiles": [
{
"keyProducerId": "exampleKeyProducerId",
"secretsFiles": [ "secrets/fileContainingSecrets.txt" ],
"secretFormat": "STRING_SHA256"
}
],
// ...You can combine vanity address generation with database lookups to enhance functionality and efficiency.
For example:
- Search for personalized (vanity) addresses using custom patterns and
- Simultaneously check if the generated addresses already exist in the LMDB database
This hybrid mode allows you to find rare or meaningful addresses while ensuring they haven’t been used before.
You can define a custom key range for private key generation—for example, limiting the search space to 64-bit keys.
In this setup, the first 192 bits (256-bit - 64-bit) of the key are zeroed, effectively restricting the generation to a specific portion of the keyspace.
This feature can be used for:
- Targeted key recovery, such as searching for private keys from known ranges like the Bitcoin Puzzle Transaction
- Verification and testing, to prove that the software functions correctly within a predictable key range
To significantly boost the performance of EC key generation, the software supports OpenCL-based parallelization.
A shared secret (base key) is transferred to the OpenCL device along with a predefined grid size. Each OpenCL thread independently derives a unique EC key by incrementing the base key with its thread ID. This allows the generation of a batch of EC keys in a single execution cycle. Once generated, the keys are transferred back to the host (CPU) memory for further processing.
The CPU then hashes the X and Y coordinates of the public keys to derive the corresponding (Bitcoin/Altcoin) addresses. This division of labor offloads the computationally expensive elliptic curve operations to the GPU, allowing the CPU to focus on faster address hashing and database lookup operations—resulting in improved overall throughput.
The OpenCL code is compatible with and tested against PoCL, an open-source, portable OpenCL implementation. PoCL lets you run the kernels without a vendor GPU driver and exposes a range of specialized targets (backends) that PoCL supports — for example CPU (host), CUDA, and other device backends — so you can pick the device that best fits your hardware. Select the desired PoCL device through the usual producerOpenCL platformIndex / deviceIndex settings (use the OpenCLInfo command to list the platforms and devices PoCL exposes).
The OpenCL backend includes a built-in self-test mechanism that cross-verifies results from the GPU against a CPU-generated reference. This ensures that the OpenCL device is functioning correctly and producing valid EC keys—giving end users confidence in the reliability of their hardware-accelerated address search.
💡 Hint: To enable the built-in self-test, make sure the following configuration flag is set to
true:"runtimePublicKeyCalculationCheck": trueThis option is disabled by default. Enabling it is especially useful during development, debugging, or when validating new hardware setups.
If you configure a BIP-39 key producer (e.g. keyProducerJavaBip39) together with OpenCL acceleration, please read this carefully.
The BIP-39 key producer creates deterministic private keys derived from your mnemonic, passphrase, and BIP32 path.
This derivation space is finite — once all child keys along the defined path are exhausted, the producer will throw
a NoMoreSecretsAvailableException. This can happen relatively quickly in GPU batch configurations.
In your JSON configuration, you may have enabled:
/**
* The batch mode will use a private key increment internal to increase the performance.
*/
public boolean batchUsePrivateKeyIncrement = true;When batchUsePrivateKeyIncrement is set to true, the OpenCL producer calls:
secrets = keyProducer.createSecrets(cProducer.getOverallWorkSize(bitHelper), cProducer.batchUsePrivateKeyIncrement);Internally this sets returnStartSecretOnly = true, meaning that only the first BIP-39-derived key is passed to the GPU.
The OpenCL kernel then uses that single key as a base key, incrementing the least significant bits (LSB) in parallel across all GPU threads to explore adjacent keyspaces.
⚠️ Use extreme caution:
- The effective search space on the GPU is not the same as the full BIP-39 derivation sequence.
- This mode is intended purely for experimental or high-performance batch testing — not for full HD-wallet traversal.
- Once all deterministic child keys are consumed, a
NoMoreSecretsAvailableExceptionwill occur.
For technical details, see:
Note: the kernel computes each public-key point once (the uncompressed form); the compressed form is derived from the same point with negligible overhead, and both the compressed and uncompressed address are checked.
Effective Keys/scounts keys (EC points), so the number of addresses examined is twice this rate — see Keys versus addresses.
| GPU Model | CPU | Key Range (Bits) | Grid Size (Bits) | keysPerWorkItem | Effective Keys/s (~) |
|---|---|---|---|---|---|
| NVIDIA RTX 3070 Laptop | AMD Ryzen 7 5800H | 256 | 22 | 256 | ~229,000,000 keys/s |
| NVIDIA RTX 3070 Laptop | AMD Ryzen 7 5800H | 256 | 24 | 2048 | ~266,000,000 keys/s |
| AMD Radeon RX 7900 XTX | AMD Ryzen 7 9800X3D | 256 | 24 | 512 | ~670,000,000 keys/s |
Methodology. Compact GPU-filter fast path, candidates/s = JMH ops/s ×
2^batchSizeInBits, all on the inlined kernel (the example configs now default tonoInlineHelpers: false). Each device is shown at its own tuned optimum — the grid optimum is device-specific: the RTX 3070 peaks atkpwi=2048, the RX 7900 XTX atkpwi=512(kpwi=2048collapses to ~71 M/s on RDNA3 through under-occupancy). The 3070'sbatch=22row is kept as a lower-grid reference. Live end-to-endFindruns (tuned FUSE_16 + BINARY_FUSE_8 cascade against the ~141 M-entry light DB, warm) sustained ~280 M/s on the 3070 and ~616 M/s on the 7900 XTX. Raw data:tune_arms.csv(RTX 3070) andtuner_ryzen9800x3d_gfx1100.csv(RX 7900 XTX).The inlined kernel is the dominant lever on AMD. At the same
24/512arm the 7900 XTX runs 187.8 M/s out-of-lined vs 669.8 M/s inlined — a ~3.6× gap that had hidden the card's real speed (an out-of-lined sweep capped atbatch=22reported only ~130 M/s). With the inlined kernel the 7900 XTX now sits ~2.3–2.5× above the 3070, as its raw compute would suggest. Trade-off: on AMD the inlined kernel's first compile is slow (~8–16 min, thencomgr-cached); NVIDIA compiles it instantly.
Grid Size (Bits)(=batchSizeInBits) andkeysPerWorkItemare device-specific — sweep your own hardware withGridSizeSweepBenchmark/TuneConfiguration.Key Range = 256is the full-width private-key path; a 160-bit MSB-Zero scan runs faster but is not separately benchmarked here. Earlier third-party rows (RTX 3090, A3000, Radeon 8060S) and their pre-optimization figures were removed as obsolete.
The table above shows the tuned peak throughput on the compact GPU-filter fast path. The table
below isolates the effect of the
GPU-side Binary Fuse 8 filter
(enableGpuFilter) at a fixed grid: with it on, the kernel checks each derived hash160 against
the on-GPU filter and transfers only the hits; with it off, every work-item result is read
back over PCIe.
Note: These numbers come from the
GpuFuse8FilterBenchmarkJMH microbenchmark (one kernel launch + PCIe read-back + host-side parse, measured in isolation). They are not directly comparable to the full-pipeline figures above — the grid size differs and there is no real consumer/queue — but the on-vs-off ratio on the same row is the metric that matters.
GPU Filter (enableGpuFilter) |
GPU Model | Key Range (Bits) | Grid Size (Bits) | Effective Keys/s (~) | Speedup |
|---|---|---|---|---|---|
| Off — full transfer (legacy) | NVIDIA RTX 3070 Laptop | 256 | 19 | 4,350,000 keys/s | 1.00× baseline |
| On — Binary Fuse 8 compact | NVIDIA RTX 3070 Laptop | 256 | 19 | 9,710,000 keys/s | ~2.2× |
Measured against a ~1 M-entry filter at batchSizeInBits = 19 (2¹⁹ ≈ 0.52 M candidates per
launch), matching the grid size of the raw key-generation table above. The
GpuFuse8FilterBenchmark JMH benchmark A/B-toggles compact mode against the legacy
full-transfer path on the same kernel and batch; numbers above are the mean of 3 measurement
iterations (compact 18.52 ± 0.05 ops/s, very stable; full transfer 8.30 ops/s, noisier) —
each op is one launch of 2¹⁹ candidates. The relevant one-time cost is OpenCL kernel compilation
(done once in setup) and GPU clock ramp-up, not JVM warmup. The filter wins by collapsing the
read-back from the full grid down to the ~0.4 % false-positive hits, so the advantage grows with
batchSizeInBits. Run it yourself:
mvn test-compile exec:java -Dexec.args="GpuFuse8FilterBenchmark"Device-side breakdown (where the time actually goes). The throughput above mixes three
costs: GPU kernel compute, the PCIe read-back, and the host-side parse of the returned grid.
To separate them, set enableProfiling: true on the OpenCL producer (or run the benchmark
with -p profiling=true); the command queue is then created with CL_QUEUE_PROFILING_ENABLE
and each launch is timestamped on the device. The same grid-19 run on the RTX 3070 reports:
| Phase | Off — full transfer | On — Binary Fuse 8 compact | Effect |
|---|---|---|---|
| GPU kernel compute | 51.5 ms | 54.8 ms | +3.3 ms (~6.5%) — the inline filter check |
| PCIe read-back (device) | 8.75 ms | 0.059 ms | ~150× less — only the ~0.4% hits cross the bus |
| Host-side parse (wall − device) | ~58.6 ms | ~0.3 ms | ~185× less — the consumer parses ~2 k hits, not 524 k results |
| Per-launch wall time | ~118.8 ms | ~55.2 ms |
The takeaway: the GPU-side filtering is not expensive on the GPU. It adds only ~6.5 % to
kernel compute (a handful of integer ops + one murmur64 hash + 3 fingerprint reads per
candidate, versus the elliptic-curve math and two SHA-256 + two RIPEMD-160 hashes that dominate
the kernel). The ~2.2× end-to-end gain comes almost entirely from collapsing the PCIe transfer
(~150×) and the host-side parse (~185×) — i.e. it offloads the address-presence check from the
CPU at negligible GPU cost, which is exactly the design intent. (Profiling adds a little driver overhead and is
off by default; it is a diagnostic switch, never enabled by the runtime pipeline.)
In Find mode the consumer logs a periodic, single-line statistics snapshot at INFO
level. The print interval is controlled by consumerJava.printStatisticsEveryNSeconds
(default 60); the Generated and -> LMDB rates are a current throughput averaged
over a trailing consumerJava.statisticsRateWindowSeconds window (default 60), independent of
the print interval. All logging goes through SLF4J / Logback, so format and destinations
are fully configurable (see Customizing Log Output).
A statistics line looks like this:
Statistics: [uptime 43 min] [Generated 128 M/s (333258 M total)] [-> LMDB 2 M/s (5196 M lookups total), 99.22% pre-filtered] [rate window 60s] [Batches per producer: exampleKeyProducerSecureRandomId (Random, GPU)=79455] [Producers running: 1] [Consumers running: 8] [Consumer ready for work (queue empty): 167939] [Producer blocked (queue full): 0] [Average contains time: 0 ms] [keys queue size: 0] [Hits: 0]
| Field | Meaning |
|---|---|
uptime N min |
Lifetime elapsed minutes since the scan started. |
Generated <rate> (<N> M total) |
The headline performance number: the rate of candidate private keys the producers fully compute (windowed rate + lifetime total in millions). On a GPU run this is the real key-generation output. It counts keys (EC points), not addresses — see Keys vs. addresses below. Auto-scales across /s, k/s, M/s, G/s. |
-> LMDB <rate> (<N> M lookups total), NN.NN% pre-filtered |
The rate of hash160 address lookups that survive the GPU pre-filter and actually reach the consumer/LMDB (windowed rate + lifetime total). pre-filtered is the share of the potential address lookups the filter eliminated before LMDB (see below); it is omitted when no pre-filter is active (full transfer). |
rate window Ns |
The trailing window (statisticsRateWindowSeconds) the two rates are averaged over. |
Batches per producer: <label>=N, … |
Dispatched-batch count per running producer, keyed by <keyProducerId> (<Strategy>, <CPU|GPU>) so concurrently running producers are told apart. The strategy (Random, Bip39, Incremental, Socket, WebSocket, Zmq) is derived from the key producer; the backend (CPU for producerJava/producerJavaSecretsFiles, GPU for producerOpenCL) from the producer. none until the first batch. |
Producers running: N |
Number of producers currently in the RUNNING state. |
Consumers running: N |
Number of consumer worker threads currently running (≤ consumerJava.threads). |
Consumer ready for work (queue empty): N |
Runtime health counter — rising is normal/healthy. Consume cycles that found the queue empty and waited. An empty queue is the desired state: it means the CPU drains everything the producers generate and has headroom. See below. |
Producer blocked (queue full): N |
Runtime health counter — rising is the warning sign. Times a producer hit a full queue and had to wait, i.e. the CPU can't keep up (CPU-bound). See below. |
Average contains time: N ms |
Mean time spent per address-presence lookup. Large values point at a slow lookup backend (see Address Lookup Backends). |
keys queue size: N |
Instantaneous depth of the producer→consumer queue (bounded by consumerJava.queueSize). |
Hits: N |
Number of address matches found so far (see Hit Logging). |
Why Generated and -> LMDB count different things: Generated counts candidate private keys — secp256k1 points, one full scalar multiplication each.
From every such key the tool derives two hash160 addresses: the compressed and the
uncompressed form, and both are checked. So the number of addresses examined is twice the
Generated rate:
Generated 128 M/s⇒ 128 M keys/s ⇒ ~256 M addresses/s (compressed + uncompressed) hashed and probed against the filter.-> LMDBcounts those address lookups (post-filter), not keys. With a strong GPU pre-filter almost none survive, so-> LMDBis a tiny fraction ofGenerated.pre-filteredis measured against that doubled figure: it is the share of the2 × Generatedpotential address lookups the pre-filter removed before LMDB. In the line above,2 M/sreach LMDB out of~256 M/spotential ⇒99.22 % pre-filtered.
This split is deliberate. An earlier version reported only the surviving (-> LMDB) rate, which
collapses when a GPU pre-filter is on — making a hard-working GPU look idle. Generated now shows
the real output rate next to it, so a healthy GPU run reads as high Generated, low -> LMDB, high
pre-filtered.
The total fields (
Generated … total,-> LMDB … total,Consumer ready,Producer blocked,Hits) are cumulative since startup — for those, what matters is how fast they rise between two lines, not the absolute value. TheGenerated/-> LMDBrates are already a current windowed rate, so they read directly with no need to difference successive lines.
The pipeline is Producers → bounded queue → Consumer (CPU address checks). The queue is
the pressure gauge, and the two counters have opposite polarity — only one of them is a
problem signal:
Producer blocked (queue full)— this is the warning sign. It rises when producers generate batches faster than the consumer can check them, so the queue fills and producers must wait input(). A sustained rise means the consumer/CPU is the bottleneck (CPU-bound). This is the counter to watch if throughput is lower than expected.Consumer ready for work (queue empty)— rising is normal and expected, not a problem. It rises when the consumer drains the queue and waits for more work. You want the queue as empty as possible: it means the CPU comfortably keeps up with the producers and has spare capacity. For GPU scanning this counter rising steadily (withkeys queue sizenear 0) is exactly the healthy operating point. It is only worth a second look if you expected the producers to saturate the CPU and they don't, or if nothing is producing (a stalled or misconfigured producer also shows as a perpetually ready consumer with zero throughput).
Producer blocked |
Consumer ready |
keys queue size |
Interpretation |
|---|---|---|---|
| ~0 | rising | ~0 | Healthy (the normal GPU-scanning state) — the CPU keeps up easily. If throughput is below expectations, the limit is the producer/GPU side, not the consumer. |
| rising | ~0 | near queueSize |
CPU-bound — the warning state. The consumer can't keep up. Use a faster lookup backend, raise consumerJava.threads, or reduce the producer rate. |
| ~0 | ~0 | mid-range | Balanced — neither side waits; watch Generated. |
| ~0 | rising | ~0 | with Generated ≈ 0 → nothing is producing — check the producer/key-producer configuration. |
Rule of thumb: Producer blocked climbing = act (CPU too slow); Consumer ready climbing = fine.
When a generated address matches the database, the consumer logs it at INFO with a clear
prefix and the full key material needed to recover the wallet:
hit: Found the address: …— the match, including the private key in WIF, the public address, and related key details.hit: safe log: …— an additional, separately formatted record written immediately on a hit so the finding survives even if later formatting fails.vanity pattern match: …— emitted when optional vanity-pattern matching (consumerJava.enableVanity/vanityPattern) is enabled and a generated address matches.
The running Hits: field in the statistics line reflects the total count. To persist hits to
their own file, route these messages with a dedicated appender (next section).
Logging is backed by Logback. A ready-to-edit configuration ships at
examples/logbackConfiguration.xml. Point the
application at a custom file with:
java -Dlogback.configurationFile=/path/to/logbackConfiguration.xml -jar bitcoinaddressfinder-<version>-jar-with-dependencies.jar config.jsonTypical customizations:
- Write hits to their own file — add a
FileAppenderand route theConsumerJavalogger (or filter on thehit:prefix) to it, so matches are never lost in console scrollback. - Adjust the statistics cadence / rate window —
consumerJava.printStatisticsEveryNSecondscontrols when the line is emitted;consumerJava.statisticsRateWindowSecondscontrols the trailing window theGenerated/-> LMDBrates are averaged over (Logback controls how/where). - Machine-readable output — the single-line, fixed-field statistics format is intentionally stable so an external supervisor or GUI can parse it (e.g. via an inter-process pipe) to plot throughput and the ready/blocked health counters over time.
Keep the CRLF guard. The bundled pattern wraps the message in
%replace(%msg){'[\r\n]+', ' | '}, which neutralizes carriage returns / line feeds in rendered messages so untrusted input cannot forge extra log lines. Preserve this wrap in any derived pattern.
Isn't it impossible to find collisions?
The likelihood of discovering a collision—two different inputs that produce the same output hash—is astronomically low when using secure cryptographic functions like SHA-256 and RIPEMD-160, which are employed in Bitcoin address generation.
However, discussions around potential vulnerabilities, theoretical attacks, or edge cases exist in the cryptography and Bitcoin communities.
For more in-depth information on collision resistance, address reuse risks, and cryptographic hash functions, refer to the following resources:
- How to deal with collisions in Bitcoin addresses – Crypto StackExchange
- Why haven't any SHA-256 collisions been found yet? – Crypto StackExchange
- bitcoin-wallet-finder – Results and discussion
- PKGenerator_Checker – Instructions
- BitBruteForce-Wallet – Requirements and usage
- New Records in Collision Attacks on RIPEMD-160 and SHA-256 (ePrint 2023/285) – Li et al. present new records in collision attacks: 40-step RIPEMD-160 and 39-step semi-free-start SHA-256. Both hash functions are fundamental to Bitcoin address generation.
In 2013, a serious vulnerability was discovered in Android’s SecureRandom implementation. It caused Bitcoin private keys to be exposed due to reused or predictable random values during ECDSA signature creation. This problem affected many wallet apps that generated keys directly on Android devices.
BitcoinAddressFinder can be used to simulate and analyze this type of attack. With small changes, it can reproduce faulty random number generators by:
- using fixed or repeating
kvalues - limiting entropy to 16, 32, or 64 bits
- replacing the secure RNG with a weak or deterministic version
This makes it possible to test and study:
- how r-collisions happen in ECDSA signatures
- how easy it is to find reused
kvalues - how quickly a private key can be recovered
- how secure different RNG implementations really are
BitcoinAddressFinder can generate millions of key pairs quickly. This allows researchers to create and scan large keyspaces under controlled RNG conditions. All results can be verified using the built-in self-test feature or compared against known addresses in the LMDB database.
This kind of simulation is useful for:
- learning about signature security
- building training examples for audits or courses
- checking RNG quality in real wallets or custom apps
References:
- Wikipedia: RNG Attack
- Crypto.SE: Android SecureRandom + r-Collisions
- The Register: Android Bug Batters Wallets
- Google Blog: SecureRandom Fixes
- bitcoin.org Alert (2013-08-11)
Issue: AlphaWallet/Web3E#31
Credits / Found by Jean-Philippe Aumasson: https://github.com/veorq
KeyID::generatePrivateKey() relies on random_buffer(), which uses tinyMT32 as its PRNG. The PRNG is initialized only once using a 32-bit seed derived from micros().
This is not a cryptographically secure RNG, and the 32-bit timer seed makes the generated private keys predictable and brute-forceable. All keys produced by the current implementation are vulnerable. A hardware CSPRNG or proper DRBG must be used instead.
Links:
- https://github.com/AlphaWallet/Web3E/blob/c19324cc209b11fd4389d1c782a7e6ffbc391cfb/src/KeyID.cpp#L48-L50
- https://github.com/AlphaWallet/Web3E/blob/c19324cc209b11fd4389d1c782a7e6ffbc391cfb/src/Trezor/rand.c#L362-L364
Although this vulnerability originates from an Ethereum-based embedded wallet, it demonstrates the same class of weaknesses relevant to Bitcoin and all ECDSA-based systems: private keys generated from low-entropy or predictable random sources.
In this implementation, the wallet uses TinyMT32 as its PRNG and seeds it only once with a 32-bit microsecond timer:
tinymt32_init(&tinymt, (uint32_t)micros());
TinyMT32 is not a cryptographically secure RNG, and seeding it with a 32-bit timestamp collapses the effective key space from 2²⁵⁶ to only 2³² possible seeds. An attacker who can approximate the device’s boot time can brute-force all feasible seeds, reproduce the PRNG stream, and reconstruct the wallet’s private keys.
This resembles the historic Android SecureRandom vulnerability: the elliptic-curve cryptography itself is secure, but the randomness used to create keys is not.
BitcoinAddressFinder can simulate this type of scenario by generating keys using intentionally weak or deterministic RNGs and scanning the resulting restricted key ranges. This makes it possible to study how insecure RNGs can compromise wallets.
How BitcoinAddressFinder (pinned first, in bold) compares to related tools. Its distinguishing mix —
GPU and CPU generation, 100+ coins, and an offline check against a local database of known/funded
addresses — is rare here: most others do just one of vanity generation, puzzle-solving, online balance
lookups, or wallet recovery. It also stays efficient against a ~10⁹-entry address database (≈1.38 billion
/ ~57 GB) via an O(1) memory-mapped LMDB lookup with an optional Binary Fuse / Blocked Bloom pre-filter
— the axis (≥10⁹ DB) most other tools do not scale on.
Legend: ✅ yes · ❌ no · ❓ could not confirm · ~ partial. Columns: Category = what the tool is for ·
GPU / CPU = compute backend the user runs · Coins = many non-Bitcoin coins · Vanity = pattern/prefix
matching · Offline = the search runs without contacting a server/API · OSS = source publicly available ·
≥10⁹ DB = efficient (O(1) / indexed) lookup against a very large (~10⁹) known-address set (✅ yes · ~
indexed but at smaller scale · ❌ linear / tiny / online / none) · Checks against = what a derived address
is matched against — Local funded DB · Target list (usually user-supplied) · Online API · Puzzle target · Own wallet · None (generate-only).
Flags verified 2026-07-20 against each project's repository or site; the few cells that stayed unconfirmable are marked ❓ — corrections via PR are welcome. Rows are grouped by Category (BitcoinAddressFinder pinned first). Categorisation is for orientation, not an endorsement.
| Project | Category | GPU | CPU | Coins | Vanity | Offline | OSS | ≥10⁹ DB | Checks against | Main goal |
|---|---|---|---|---|---|---|---|---|---|---|
| BitcoinAddressFinder | Address-DB scan | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Local funded DB | Scan random/sequential keys for 100+ coins vs a local LMDB address database; optional vanity |
| LBC | Address-DB scan | ✅ | ✅ | ❌ | ❌ | ❌ | ❓ | ~ | Local funded DB | Distributed pool scanning the hash160 space for funded-address collisions (legacy) |
| privatekeys.pw — scanner | Address-DB scan | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ~ | Local funded DB | In-browser random-key scanner against a hosted funded-address filter |
| allprivatekeys.com — get lucky | Address-DB scan | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | Online API | Browse server-generated key pages (BTC/BCH/BTG) with tx/balance flags |
| treyyoder/bitcoin-wallet-finder | Address-DB scan | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | Online API | Random keys, balances checked via the blockchain.info API (demo) |
| Frankenmint/PKGenerator_Checker | Address-DB scan | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | Online API | Random keys, balances checked via an online explorer API (demo) |
| Henshall/BitcoinPrivateKeyHunter | Address-DB scan | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | Target list | Random keys vs a user-supplied local list of known-funded addresses |
| Xefrok/BitBruteForce-Wallet | Address-DB scan | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | Local funded DB | Random keys vs a bundled local ~123k-address list (linear scan) |
| Isaacdelly/Plutus | Address-DB scan | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ~ | Local funded DB | Sequential keyspace walk vs a local funded-wallet DB (Bloom filter) |
| Noname400/Hunt-to-Mnemonic | Address-DB scan | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ~ | Local funded DB | BIP39 / seed hunt vs local Bloom-filter address databases |
| Py-Project/Bitcoin-wallet-cracker | Address-DB scan | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | Target list | Random BIP39 wallets vs a local address list (offline GUI) |
| vlnahp/KeyZero | Address-DB scan | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | Local funded DB | Random/sequential keys vs a local address list (or online API) |
| albertobsd/keyhunt | Puzzle solver | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | Target list | CPU hunter with several modes (address, BSGS, xpoint) over a bit range |
| brichard19/BitCrack | Puzzle solver | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | Target list | GPU brute-force of a keyspace range for target addresses |
| kanhavishva/KeyHunt-Cuda | Puzzle solver | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | Target list | CUDA hunter for puzzle/target addresses over a range (VanitySearch-based) |
| privatekeys.pw — cloud search | Puzzle solver | ❓ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | Puzzle target | Rent cloud GPUs to scan for the Bitcoin puzzle (pool) |
| ilkerccom/bitcrackrandomiser | Puzzle solver | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | Puzzle target | Pool client that drives BitCrack for the Bitcoin puzzle |
| btcpuzzle.info | Puzzle solver | ✅ | ❓ | ❌ | ✅ | ❌ | ✅ | ❌ | Puzzle target | Solo-pool platform + open-source client for the Bitcoin puzzle |
| JeanLucPons/VanitySearch | Vanity generator | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | None | GPU/CPU vanity address generator (prefix + wildcards) |
| samr7/vanitygen | Vanity generator | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | None | The original vanity generator (CPU + OpenCL, regex) |
| 10gic/vanitygen-plusplus | Vanity generator | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | None | Multi-coin vanity generator (100+ coins; CPU / OpenCL) |
| oritwoen/vgen | Vanity generator | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | None | Regex-driven vanity generator (Rust; wgpu or CPU) |
| allprivatekeys.com — vanity | Vanity generator | ❌ | ✅ | ❌ | ✅ | ✅ | ❓ | ❌ | None | In-browser vanity generator; keys stay local (can run offline) |
| gurnec/btcrecover | Wallet recovery | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | Own wallet | Recover your own wallet password / BIP39 seed (optional AddressDb) |
| Coding-Enthusiast/FinderOuter | Wallet recovery | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | Own wallet | Reconstruct your own damaged key / seed / address (GUI) |
| johncantrell97/bip39-solver-gpu | Wallet recovery | ✅ | ❓ | ❌ | ❌ | ✅ | ✅ | ❌ | Own wallet | GPU brute-force of missing BIP39 words for a known address |
| JeanLucPons/BTCCollider | Research / collision | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | None | Find RIPEMD-160 address collisions (birthday paradox) |
| mvrc42/bitp0wn | Research / collision | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | None | Educational ECDSA nonce-reuse / discrete-log attack demos |
| JamieAcharya/Bitcoin-Private-Key-Finder | Novelty / utility | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | Target list | Random keys vs one user-supplied target address (novelty) |
| mingfunwong/all-bitcoin-private-key | Novelty / utility | ❌ | ✅ | ❌ | ❌ | ❓ | ✅ | ❌ | None | Web page to browse the whole key space (no balance check) |
| prof7bit/wallet-key-tool | Novelty / utility | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | None | GUI to import/export/convert wallet keys (no search) |
An export of the full database can be used to predict private keys with deep learning. A funny idea: https://github.com/DRSZL/BitcoinTensorFlowPrivateKeyPrediction
Laptops with hybrid graphics—using both integrated (iGPU) and discrete (dGPU) GPUs—may suffer from significantly reduced performance when running compute-intensive OpenCL workloads like BitcoinAddressFinder. This is often due to:
- Shared memory between CPU and GPU
- Bandwidth limitations
- Automatic GPU switching (e.g., NVIDIA Optimus, AMD Enduro)
- Suboptimal GPU selection by drivers
| Manufacturer | Affected Series/Models | Recommendation |
|---|---|---|
| HP | ZBook G3, G4, G5, G7 | Enter BIOS → set Graphics to Discrete Only |
| Lenovo | ThinkPad X, T, and P Series (hybrid configs) | Use Lenovo Vantage → Disable Hybrid Graphics |
| Dell | Inspiron, XPS, Precision (with Optimus) | BIOS → Disable Hybrid Mode (if available) |
| MSI | Gaming laptops with switchable graphics | Use Dragon Center to select dGPU |
| Razer | Blade models with NVIDIA Optimus | Use Razer Synapse to enforce dGPU use |
| Apple (Intel) | MacBook Pro (pre-2021 with dual GPUs) | macOS → Disable Automatic Graphics Switching in Energy Preferences |
If your laptop uses hybrid graphics, always ensure that the discrete GPU is explicitly selected for OpenCL workloads to avoid severe performance bottlenecks.
⚠️ DO NOT UPGRADE jqwik past 1.9.3. jqwik 1.10.0 added an anti-AI prompt-injection string to test stdout; the 1.10.1 user guide states the library "is not meant to be used by any 'AI' coding agents at all." 1.9.3 is the last pre-disclosure release and is the pinned version. SeeCLAUDE.mdsection "jqwik prompt-injection in test output" for the full context. Dependabot is configured to ignore allnet.jqwikupdates (every version, including patches) — see theignorerule in.github/dependabot.yml.
- Expand PIT mutation-testing scope. PIT is wired in
pom.xmland runs on every CI build (in the ubuntu-latest leg of thetestjob) with<mutationThreshold>100</mutationThreshold>, but<targetClasses>is currently narrowed to a single class (BitHelper). The intent is to exercise the wiring and gate against regressions on that single class today; widen<targetClasses>incrementally as additional classes reach mutation-test parity. Final target:<param>net.ladenthin.bitcoinaddressfinder.*</param>matching the streambuffer pattern (excluding native/OpenCL-bound code where mutation testing is impractical). - Refactor the entire key generation infrastructure to support a key provider. This provider should be configurable to supply private keys from various sources, such as Random, Secrets File, Key Range, and others. All consumers should retrieve keys from this provider.
- Key generation within a specific key range. See #27 Wished from themaster:
"privateKeyStartHex" : "0000000000000000000000000000000000000000000000037e26d5b1f3afe216"
"privateKeyEndHex" : "0000000000000000000000000000000000000000000000037e26d5b1ffffffff"
Wished from Ulugbek:
// Search started from given address. Would be nice if it can save last position...
"sequentalSearch" : true,
"startPrivateKey" : xxxxxxxx,
// Random search with batches, here 100000. I,e. some random number is found and after 100000 sequental addresses should be checked.
"searchAsBatches" : true,
"searchBatchQuantity" : 100000,
// Random search within Address Space, with batches, here 100000.
"searchAsBatches" : true,
"searchAddressStart" : xxxxxxx,
"searchAddressEnd" : xxxxxxxy,
"searchBatchQuantity" : 100000
- Incomplete Seed-Phrase as Private KeyProvider. Wished from @mirasu See #38
- Socket KeyProvider for independend KeyProvider via byte protocol
- Ideas might be a screen recorder and use the visible screen downscaled as 256 bit input
- KeyProvider must get the grid size to increment properly on incremental based Producer
- ExecutableKeyProvider gets data from stdout
BitcoinAddressFinder stands on the shoulders of excellent open-source work. In particular:
- hashcat (hashcat.net) —
the OpenCL secp256k1 elliptic-curve, SHA-256 and RIPEMD-160 routines that power GPU key generation
are ported from hashcat's MIT-licensed kernels in
OpenCL/(vendored here undersrc/main/resources/copyfromhashcat/). Huge thanks to the hashcat project and its contributors. - bitcoinj — Bitcoin/altcoin key derivation and address encoding used across the CPU path and as the reference for the GPU parity tests.
- LMDB / lmdbjava — the high-performance memory-mapped address database.
- JOCL — Java bindings to OpenCL.
- Binary Fuse filters — by Thomas Mueller Graf and Daniel Lemire, with the SipHash construction by Jean-Philippe Aumasson and Daniel J. Bernstein.
- vanitygen (Samr7) and vanitygen-plusplus (10gic) — pioneering CPU/OpenCL Bitcoin vanity-address searchers; studied for their GPU EC key-generation techniques.
- bitcoin-core/secp256k1 — the reference high-performance secp256k1 library (reduced-radix field, GLV endomorphism, safegcd modular inverse); the basis for several of the EC optimizations and a reference for correctness.
- bitcoinj/secp256k1-jdk — secp256k1 bindings for the JVM; a reference for the Java-side secp256k1 surface.
See the bundled third-party licenses for full attribution; some subprojects are under their own
licenses (e.g. the copyfromhashcat kernels are MIT).
BitcoinAddressFinder is not intended for malicious use, and you are solely responsible for complying with your local laws, international regulations, and ethical standards.
This software must not be configured or used to attempt unauthorized access to cryptocurrency assets (e.g., scanning for RIPEMD-160 address collisions to gain access to third-party funds).
Such activities are likely illegal in most jurisdictions and may carry serious penalties.
You may use BitcoinAddressFinder for legitimate and research-focused purposes, such as:
- Recovering lost private keys associated with your own known public addresses
- Verifying whether generated addresses have ever been used to prevent collisions
- Running performance benchmarks and OpenCL testing
- Generating vanity addresses for personal or demonstrative use
- Conducting offline cryptographic research and educational exploration
You must not use this tool for:
- Gaining unauthorized access to cryptocurrency or wallet funds
- Circumventing access controls or exploiting systems
- Engaging in unethical behavior or violating terms of service
Below is a non-exhaustive collection of legal frameworks that may apply depending on your jurisdiction.
⚠️ Important Note: The following information is provided for informational purposes only and does not constitute legal advice.
You should always consult with a qualified legal professional before engaging in any activity related to cryptographic systems or asset recovery.
I merely present relevant legal context, not binding interpretations or actionable recommendations.
-
§ 202c StGB – Vorbereiten des Ausspähens und Abfangens von Daten
(Preparation of spying or intercepting data) -
OLG Braunschweig, Beschluss vom 18.09.2024 – 1 Ws 185/24
In einem aufsehenerregenden Fall entschied das OLG, dass der Zugriff auf eine Wallet mittels eines bekannten (nicht rechtswidrig erlangten) Seeds nicht als Straftat im Sinne der §§ 202a, 263a oder 303a StGB gewertet werden kann.
Der Angeklagte hatte eine Wallet für einen Dritten erstellt und sich später mittels der Seed-Phrase Zugriff auf Token im Wert von rund 2,5 Mio. € verschafft.
Das Gericht urteilte jedoch:- Kein Diebstahl: Kryptowährungen sind keine „Sachen“ im Sinne des § 242 StGB.
- Kein Ausspähen von Daten (§ 202a StGB): Der Zugriff mittels bekannter Passwörter ist kein Überwinden einer Zugangssicherung.
- Kein Computerbetrug (§ 263a StGB): Eine Krypto-Transaktion impliziert keine „Täuschung“ oder „Miterklärung einer Berechtigung“.
- Keine Datenveränderung (§ 303a StGB): Die eigentliche Änderung erfolgt durch die Blockchain-Netzwerkbetreiber – nicht durch den User selbst.
🔍 Fazit: Der „Kryptodiebstahl“ per bekanntem Seed ist unter Umständen nicht strafbar – bleibt aber zivilrechtlich angreifbar.
Quelle: OLG Braunschweig Beschluss 1 Ws 185/24 (juris.de)
Bericht: heise.de Artikel vom 11.07.2025
-
Computer Fraud and Abuse Act (CFAA)
Prohibits unauthorized access to protected computers, including use of tools for circumvention -
Digital Millennium Copyright Act (DMCA)
Sections on anti-circumvention tools and reverse engineering may apply in specific contexts
-
General Data Protection Regulation (GDPR)
While not directly related, use of personally identifiable data or address targeting must comply -
Directive 2013/40/EU on Attacks Against Information Systems
Criminalizes the creation or possession of tools designed for unauthorized access
- UK: Computer Misuse Act 1990
- Canada: Criminal Code Sections 342.1 & 430
- Australia: Criminal Code Act 1995 – Part 10.7 – Computer Offences
⚠️ The authors and maintainers of BitcoinAddressFinder assume no responsibility for how the tool is used.
It is your duty to ensure compliance with all relevant legal and ethical standards in your country and jurisdiction.
If in doubt, consult with a legal professional before using this software for anything beyond educational or personal purposes.
It is licensed under the Apache License, Version 2.0. See LICENSE for the full license text. Some subprojects have a different license.
This package is Treeware. If you use it in production, then we ask that you buy the world a tree to thank us for our work. By contributing to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats.