Lock-free early-exit
OnceLock<SearchMatch> replaces the classic Mutex + AtomicBool pair. The first thread to find a hit publishes the result; the rest exit through rayon’s work-stealing scheduler with zero contention.
A research-grade Rust engine that finds secp256k1 scalars with sub-second throughput. Powered by 512-variant range-splitting, Montgomery batch inversion, and a Radix16 fixed-base table — wired together with zero unsafe in the hot path.
Given a secp256k1 target P = d·G, the engine searches for scalars j and offsets V such that x(j·G) = x(P − V·G) , yielding candidate keys d = V ± j (mod n) — a textbook multi-variant range-splitting implementation, end-to-end tested.
The search space is decomposed via powers-of-two and cumulative-sum offsets. Metadata is interned OnceLock<[OffsetVariant; 512]> — zero allocations per session.
One inversion per batch_size-point batch instead of N. Drops the normalization phase by ~15–20× on a default batch of 32.
A 33-entry lookup table (~30 KB static, lazily built) roughly halves the bootstrap scalar-mul cost. Measured end-to-end speedup: 2.80×.
Every subsystem is documented in an ADR or optimization decision. Trade-offs are explicit, not accidental.
OnceLock<SearchMatch> replaces the classic Mutex + AtomicBool pair. The first thread to find a hit publishes the result; the rest exit through rayon’s work-stealing scheduler with zero contention.
Write-then-rename persists every boundary with an integrity anchor. Crash mid-sweep, restart from the exact j — no duplicate work, no corrupted state.
Vec<ProjectivePoint> sized against Config::batch_size (1..=256). --batch-size is finally honoured at runtime; peak heap is bounded by your input, not a hard-coded constant.
Optional precomputation persists X-coordinates to disk. On NVMe, the cached sweep path hits 10⁸ scalars/sec — about 100× the CPU-bound path. Auto-disabled in address mode.
tracing + tracing-subscriber with non-blocking daily-rolling files. RUST_LOG-style filtering, audit boundaries every TRILLION scalars, and rayon-panic-safe worker handlers.
KAT against SEC1 §2.7.1, differential vs libsecp256k1, end-to-end audit, randomized integration, and six cargo-fuzz targets — all reproducible with a single cargo test.
Curated pedantic + nursery clippy sets gated by -D warnings. Documented allow-list, no #[allow(unused)] scattered in the source. If clippy is silent, the code is clean.
find::search owns no I/O. All persistence is behind the CacheWriter trait — the hot loop stays pure, testable, and free of side effects.
find::config, find::ecc, find::orchestrator, find::search, find::error. Result<Option<SearchMatch>, FindError> at every public boundary. #![warn(missing_docs)] on the crate root.
Reproducible on an Apple M3 Pro at opt-level=3, lto=fat, criterion 100 samples. Numbers are pinned to a single canonical commit and propagated identically through the changelog and benchmarks doc.
10M scalars end-to-end
vs 5.27 ms before precomputed-tables
End-to-end speedup
Radix16 fixed-base table (commit series)
Less per-batch cycles
chain + Montgomery + match, batch_size=64
Cached-sweep speedup
NVMe binary cache vs CPU path
| Benchmark | Before | After | Speedup |
|---|---|---|---|
| plus_g_chain / chain_32_plus_g | 33.78 µs | 17.99 µs | 1.88× |
| plus_g_chain / naive_32_scalar_muls | 919.88 µs | 423.73 µs | 2.17× |
| match_x / aligned_slice | — | 31.6 ns | — |
| batch_normalization / 32_points | — | 38.5 µs | ~15–20× |
| end_to_end_small_scalar_12345 / 10M | 5.27 ms | 1.886 ms | 2.80× |
Same engine, two entry points. Config::new vs Config::new_address_mode select the surface — the hot loop stays identical and the verification layers apply to both.
Search the full scalar space for an X-coordinate match against a SEC1 compressed public key. The traditional variant-keyed sweep used by the discovery engine.
find --pubkey 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 find --pubkey 0279be66... \
--cache-points \
--batch-size 64 \
--variants 256 Search a user-specified scalar range [from, to] for the scalar behind a Bitcoin mainnet P2PKH/P2SH address. Hash40 compare keeps the inner loop cheap.
find --address 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa \
--from 1 --to 100000000 find --address 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa \
--from 0x1 --to 0x5F5E100 The orchestrator is a thin coordinator. The heavy lifting lives in find::search — a pure, I/O-free module whose entire surface is exercised by 200+ integration cases.
parse_pubkey stage
SEC1 compressed · 33 bytes · deep validate
src/ecc.rs generate_variants stage
512 × (label, offset, x_bytes) · OnceLock-interned
src/search.rs VariantIndex stage
flat sorted Vec, L1-resident, O(log 512)
src/search.rs sweep_parallel stage
rayon · batch_size · match_x binary search
src/search.rs batch_normalize stage
Montgomery simultaneous inversion · 1 + N mults
k256 / ProjectivePoint publish match stage
OnceLock<SearchMatch> · lock-free early-exit
src/orchestrator.rs checkpoint stage
atomic write-then-rename · fsync · JSON
src/persistence.rs Every public function has a unit test. Every pipeline has an integration test. Every change is gated by the same five commands CI runs — no separate local recipe.
13
Known-answer tests against SEC1 §2.7.1 vectors and k256 reference outputs, plus a to_hex_x ↔ x_bytes round-trip regression.
12
Cross-check vs the reference C libsecp256k1 across boundary scalars (1, 2, 7, 100, 1k, 1M, 1.2G, 2³², 2⁶³, u64::MAX, …).
21
End-to-end pipeline (parse → variants → sweep → recover) for the known scalar 1234567890, plus a 20-case proptest over [2, 10_000].
60+
Randomized discovery, prop_batch_size_runtime (1..=256), prop_search_finds_any_scalar_in_range, edge cases.
6
cargo-fuzz targets for every public API: parse_pubkey, parse_pubkey_roundtrip, hex_to_scalar, scalar_mul_g, generate_variants, match_x.
Suite size
71
lib unit tests
200+
integration
13
KAT vectors
12
differential
6
fuzz targets
9
criterion benches
cargo fmt --all -- --check cargo clippy --all-targets --all-features -- -D warnings cargo test --all-targets --all-features cargo test --doc RUSTDOCFLAGS=-D warnings cargo doc --no-deps --all-features Install from crates.io, build from source, or use the library API. The whole crate compiles in seconds on a modern laptop and produces a single statically-linked binary.
# Install
$ cargo install find
# Or build from source
$ git clone https://github.com/sachncs/find
$ cd find && cargo build --release
# Run
$ find --pubkey 0279be66...
"match": null,
"probed": 1247823104,
"elapsed_secs": 2.84