Note: The code of this project was built by gpt-5.6-sol high with little human supervision.
walgrind searches the unhardened BIP-32 index range of a Bitcoin output
descriptor for a target address. It uses every available logical CPU by
default and can derive wildcard public keys in parallel on an NVIDIA GPU.
Inputs contain public keys only. walgrind rejects xprv/tprv and WIF private
keys, performs no network requests, and stops after verifying the first match.
It accepts either:
- A public ranged BIP-380 output descriptor containing exactly two aligned
BIP-389 multipath branches, such as
/<0;1>/*. - A supported subset of BIP-388 wallet descriptor templates plus the ordered
key-information vector.
/**expands to/<0;1>/*.
Install the latest release from crates.io with Rust 1.85 or newer. The default build uses the CPU backend:
cargo install --locked walgrind
walgrind --versionTo build the development version from source instead:
git clone https://github.com/bigspider/walgrind.git
cd walgrind
cargo build --release
./target/release/walgrind --helpUse a release build for real searches. A full branch contains 2,147,483,648 indexes and can take a long time even across many CPU cores.
CUDA support is opt-in and currently targets Linux. It requires an NVIDIA
driver, a GPU with compute capability 7.5 or newer, nvcc, the CUDA runtime,
ar, and a compatible C++ host toolchain:
cargo install --locked walgrind --features cuda
walgrind --list-cuda-devicesFrom a checkout, use:
cargo build --release --features cuda
./target/release/walgrind --list-cuda-devicesThe build always embeds forward-compatible compute_75 PTX. When nvcc
supports the architecture reported by the installed driver, it also embeds
native SASS. A CUDA 12.0 toolkit can therefore run on Blackwell GPUs through
driver JIT compilation even though it cannot emit native sm_120 code.
For cross-builds, set WG_CUDA_ARCH to a capability such as 89, 8.9,
sm_89, or compute_89.
This copy-pasteable example finds receiving index 1 in a three-index range:
walgrind \
--descriptor 'wpkh([6738736c/84h/0h/2h]xpub6CRQzb8u9dmMcq5XAwwRn9gcoYCjndJkhKgD11WKzbVGd932UmrExWFxCAvRnDN3ez6ZujLmMvmLBaSWdfWVn75L83Qxu1qSX4fJNrJg2Gt/<0;1>/*)' \
--branches receiving \
--target bc1qx2pzftfcz0aqtq7du72m8nrtw96vcfyzhk237g \
--end-index 2 \
--threads 1The stable stdout record is:
MATCH branch=receiving index=1 address=bc1qx2pzftfcz0aqtq7du72m8nrtw96vcfyzhk237g script_pubkey=0014328224ad3813fa0583cde795b3cc6b7174cc2482
Run with no arguments in a terminal for guided prompts:
walgrindDirect descriptor input:
walgrind \
--descriptor 'wpkh([d34db33f/84h/0h/0h]xpub.../<0;1>/*)' \
--branches both \
--target bc1q...BIP-388 template input:
walgrind \
--template 'wsh(sortedmulti(2,@0/**,@1/**))' \
--key "[aaaaaaaa/48'/0'/0'/2']xpub..." \
--key "[bbbbbbbb/48'/0'/0'/2']xpub..." \
--branches receiving \
--target bc1q...Quote descriptor and template arguments because *, <, >, and # have
special meanings in common shells. Keep the value on one physical line:
line breaks inside shell quotes become descriptor characters and are invalid.
The first multipath choice is receiving and the second is change. With
/<0;1>/*, these are branches 0 and 1 respectively.
The network is inferred from every xpub/tpub. --network mainnet or
--network testnet asserts that inference; a conflict is rejected. The target
must use the same network. Signet and regtest targets are not accepted.
The CPU backend dynamically assigns small contiguous batches. It uses every logical CPU reported by the operating system unless constrained:
walgrind ... --threads 8
walgrind ... --threads 1Select CUDA explicitly on a CUDA-enabled build:
walgrind ... --backend cuda --cuda-device 0--threads is CPU-only. --cuda-device and --cuda-batch-size are CUDA-only;
mixing backend-specific options is rejected.
The CUDA backend keeps BIP-32 derivation, descriptor hashing, Taproot construction, and full target comparison on the GPU. Only match and invalid derivation records return to Rust, and every reported match is rederived on the CPU before it is printed.
A process-wide 64 MiB generator table is built and validated at startup. If
that allocation fails, the backend warns and uses the lower-memory w8 table.
Automatic batch allocation starts at 524,288 indexes and halves to a minimum
of 16,384. An explicit --cuda-batch-size is exact; allocation failure is
fatal. CUDA finishes an already-launched batch before reporting a match or
honoring cancellation.
The vendored arithmetic license, hashes, and provenance are under
cuda/vendor/ultrafastsecp256k1.
The default range is 0..=2147483647. Use inclusive bounds for testing or
splitting work:
walgrind ... --start-index 1000000 --end-index 1999999Matches are the only stdout output. Progress, warnings, and the final summary go to stderr. This makes stdout directly usable by scripts: a successful search with empty stdout exhausted the requested range without a match.
On Ctrl-C, CPU workers finish claimed batches and CUDA finishes the in-flight
batch before printing the next safe --start-index. Some completed work can
be repeated after resuming, but no index is skipped. No checkpoint file is
written.
Exit status is 0 after a match or complete exhaustion, 130 after Ctrl-C, 2 for invalid input, and 1 for fatal runtime failures.
The reproducible benchmark compares one CPU thread, a configurable multicore count, every available CPU thread, and CUDA. It reports grinding speed, derived keys per second, and estimated one- and two-branch exhaustion time:
cargo run --release --features cuda --example cuda_benchSee benchmarks/README.md for the protocol and recorded RTX 5090 / CUDA 12.0 results.
- Public xpub/tpub input only; WIF, xprv, and tprv secrets are rejected.
- Exactly two ranged multipath branches.
- Address-bearing
pkh,wpkh,sh,wsh, andtrdescriptors supported by the Rustminiscriptcrate.raw,addr,combo, and bare P2PK are not. - BIP-388 placeholders must use
@i/**or@i/<M;N>/*; optional vendor derivation patterns andmusigkey expressions are not implemented. - Mainnet and Bitcoin testnet targets only; signet and regtest are not.
- CUDA failures and unsupported plans are fatal; the CLI never silently falls back to CPU.