Skip to content

Commit 1457088

Browse files
3rdIteration3rdIteration
andauthored
Make secp256k1 backend pluggable (coincurve/wallycore/pure-python) (#746)
* Make secp256k1 backend pluggable (coincurve/wallycore/pure-python) - Add btcrecover/crypto_backends.py with auto-selection of coincurve, wallycore, or a bundled pure-Python fallback (with a startup warning) - Support BTCR_BACKEND env override to force a backend - Route EC operations through the backend in btcrseed, btcrpass (incl. Electrum 2.8 ECIES) and P2TR_tools - requirements.txt: coincurve optional, wallycore>=1.0.0 added - Add benchmark_crypto_backends.py - Linux CI now tests all three backends (Latest/Weekly base+full, Termux) - Document the three backends and relative performance in INSTALL.md * Fix CI: make coincurve install resilient, drop wallycore from Termux - coincurve install now installs requirements minus coincurve first (pulling in wallycore as fallback) and only then attempts coincurve, tolerating build failures (e.g. Python 3.14) instead of hard-failing the install step - Drop wallycore from the Termux matrix: it has no Android/aarch64 wheel and its source build fails under Termux, so only coincurve and pure-python are tested there * Correct coincurve 3.14 guidance; keep resilient coincurve install fallback - Verify via PyPI: neither coincurve 20.0.0 (wheels cp38-cp312) nor 21.0.0 (wheels cp39-cp313) ships a Python 3.14 wheel, and both fail to build from source on 3.14. So coincurve simply cannot be installed on 3.14 today. - Keep the CI coincurve install as 'coincurve==21.0.0 || coincurve==20.0.0 || true' (gets a wheel on 3.10-3.13) with '|| true' so 3.14 falls back to the already installed wallycore backend instead of failing the install step - Update docs/INSTALL.md and skills/install-btcrecover/windows/SKILL.md: the previous claim that 'coincurve==20.0.0 has a 3.14 wheel' is incorrect; on 3.14 users should install wallycore (or rely on the pure-Python fallback) * Use per-Python-version coincurve pin in CI - 3.13 -> coincurve==21.0.0 (only version with a 3.13 wheel) - 3.10/3.11/3.12 -> coincurve==21.0.0 || 20.0.0 (both have wheels) - 3.14 -> no coincurve wheel; install allowed to fail and the job runs on the already-installed wallycore fallback backend * Pin coincurve per Python version in requirements files Use PEP 508 python_version markers so pip installs the coincurve release that actually has a prebuilt wheel for the running interpreter: - python_version < 3.13 -> coincurve==20.0.0 (wheels cp38-cp312) - python_version >= 3.13 and < 3.14 -> coincurve==21.0.0 (cp313 wheel) - python_version >= 3.14 -> omitted (no coincurve wheel exists); BTCRecover falls back to the wallycore backend instead This replaces the previous platform_machine-based split, which is no longer needed since both releases now ship aarch64 and x86_64 wheels. Updated in requirements.txt, requirements-full.txt, and the commented example in requirements-walletfinder.txt. * Fix CI: update stale walletfinder tests, make Termux test pure-python only - test_walletfinder.py: the 3 argument-parsing tests referenced old argparse attribute names (args.wallet_mode / args.text_mode) that no longer exist after the backward-compat flags were renamed to *_compat. Update them to the current API (skip_wallet_mode default, text_mode_compat, wallet_mode_compat). These 3 errors were failing every Linux/Windows/macOS test run (pre-existing on master). - termux-tests.yml: on Android/aarch64 neither coincurve nor wallycore has a wheel or builds from source (and bip-utils forces coincurve), so Termux can only run the bundled pure-Python backend. Test that backend only, excluding the unbuildable C libraries from the install. * Termux Full: exclude bip-utils-dependent packages (they force coincurve, which cannot build on aarch64) * Revert Termux workflow to master-equivalent setup Termux/aarch64 cannot use coincurve (ofek/coincurve#189: build/runtime incompatibility), so restore the original single-backend Termux jobs. Exclude wallycore from the main install so only coincurve is attempted (matching the historical Termux setup), avoiding the wallycore-from-source failure introduced by adding wallycore to the base requirements. * Fix CI install on Windows: replace 'pip install -r /dev/stdin' with temp file On Windows, 'pip install -r /dev/stdin' fails with 'Could not open requirements file: /proc/self/fd/0' (no /proc). Write the filtered requirements to $RUNNER_TEMP/btcr-reqs.txt and install from that file instead. This makes the coincurve install step (which previously failed on all Windows Python versions) work, falling back to the wallycore wheel on 3.14. * Remove literal backslashes around RUNNER_TEMP path in CI install steps The YAML block scalar preserved the backslash-escaped quotes as literal characters, so the shell received a path wrapped in literal backslashes and the temp requirements file was never created. Use plain double quotes. * Fix Weekly/Latest Full on Python 3.14: exclude bip-utils subtree bip-utils (and py_crypto_hd_wallet, slip10, stellar_sdk) hard-require coincurve, which has no wheel and cannot build on Python 3.14. Exclude that subtree from the Full requirements install on 3.14 so the job installs using the wallycore fallback instead of failing on a coincurve source build. * Termux Full: exclude bip-utils subtree (coincurve cannot build on aarch64) Mirrors the Weekly/Latest Full fix: bip-utils and its dependents hard-require coincurve, which has no aarch64 wheel and fails to build on Termux, so exclude that subtree from the Termux Full install. * Docs: recommend Python 3.13, warn about coincurve on 3.14+ and Termux * benchmark: add --backend flag to force a backend, record it in results * Add pure-python fallback backends for AES, ChaCha20-Poly1305, and Keccak - btcrecover/aes_backends.py: Unified AES & ChaCha20-Poly1305 backend (pycryptodome -> pure-python fallback) - lib/aes_gcm.py: Pure-python AES-GCM using pyaes + GHASH - lib/chacha20_poly1305.py: Pure-python ChaCha20 + Poly1305 (RFC 8439) - lib/keccak.py: Pure-python Keccak-256 (Ethereum-compatible) - lib/eth_hash/backends/purepython.py: eth_hash backend wrapping lib.keccak - btcrpass.py: Use aes_backends instead of direct Crypto.Cipher imports; improve protobuf error handling (exit -> ValueError/False) - emip3.py: Use chacha20_poly1305_new from aes_backends - requirements.txt: Document optional dependencies - CI: Exclude pycryptodome/protobuf in purepython backend scenario so the pure-python fallbacks are actually exercised * Fix: add __name__ to _AESModule for load_aes256_library callers load_aes256_library() returns _AESModule (the pure-python fallback), but callers access __name__ on the result to estimate password speed. Without __name__, the CI pure-python scenario crashed with: AttributeError: '_AESModule' object has no attribute '__name__' * Fix Poly1305 reduction in pure-python ChaCha20-Poly1305 The old 5-limb carry-chain decomposition was broken — when h values exceeded 32 bits, it produced integers too large for struct.pack('<I', ...), causing OverflowError in the pure-python backend CI scenario. Rewrite _poly1305_mac using clean big-integer arithmetic (int.from_bytes / to_bytes) that is provably correct and matches RFC 8439 test vectors. Also fix a minor struct.pack call in the section about patating the other. Bug found by CI testing with pycryptodome excluded. * Fix pure-python AES-GCM: correct inc32 and support non-12-byte IVs - _inc32 now increments only the low 32 bits (GCM spec), preventing values >128 bits that caused OverflowError: int too big to convert - _compute_j0 handles arbitrary-length IVs via GHASH (SP 800-38D), fixing Metamask/btc_com wallets that use 16-byte IVs * Document that simple 1-2 BIP39 word recoveries work with pure-python backends in under 24h * Fix: wallycore is ~2× slower than coincurve for BIP39 recovery * Add --backend option to benchmark.py to force secp256k1 backend Sets BTCR_BACKEND env var for all subprocesses so users can compare coincurve, wallycore, and pure-python performance from a single tool. * fixes --------- Co-authored-by: 3rdIteration <stephen@3rditeration.com>
1 parent 228f236 commit 1457088

30 files changed

Lines changed: 1851 additions & 179 deletions

.github/workflows/Latest-Run-All-Tests_Base.yml

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,53 @@ jobs:
1616
matrix:
1717
os: [ubuntu-24.04] # Test Ubuntu Only
1818
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] # Test all supported versions of Python
19+
backend: [coincurve, wallycore, purepython] # Test all three secp256k1 backends
1920

2021
steps:
2122
- uses: actions/checkout@v7
2223
- name: Set up Python ${{ matrix.python-version }}
2324
uses: actions/setup-python@v5
2425
with:
2526
python-version: ${{ matrix.python-version }}
26-
- name: Install dependencies
27+
- name: Install dependencies (coincurve backend)
28+
if: matrix.backend == 'coincurve'
2729
run: |
2830
python -m pip install --upgrade pip
29-
pip install coincurve==21.0.0 || pip install coincurve==20.0.0
30-
pip install -r requirements.txt
31+
# Install everything except coincurve first (this pulls in wallycore, the
32+
# fallback backend), then try to add coincurve on top. On platforms where
33+
# coincurve cannot be built (e.g. Python 3.14), the job still runs using
34+
# the wallycore fallback instead of failing the install step.
35+
grep -viE 'coincurve' requirements.txt > "$RUNNER_TEMP/btcr-reqs.txt"
36+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
37+
# Per-Python-version coincurve pin (matches available prebuilt wheels):
38+
# 3.9 / 3.10 / 3.11 / 3.12 -> either 20 or 21 has a wheel
39+
# 3.13 -> only 21.0.0 has a wheel
40+
# 3.14 -> neither has a wheel; the install is allowed to fail and the job
41+
# runs on the already-installed wallycore fallback backend.
42+
case "${{ matrix.python-version }}" in
43+
3.13) pip install coincurve==21.0.0 || true ;;
44+
3.14) pip install coincurve==21.0.0 || pip install coincurve==20.0.0 || true ;;
45+
*) pip install coincurve==21.0.0 || pip install coincurve==20.0.0 || true ;;
46+
esac
47+
- name: Install dependencies (wallycore backend)
48+
if: matrix.backend == 'wallycore'
49+
run: |
50+
python -m pip install --upgrade pip
51+
pip install wallycore
52+
grep -viE 'coincurve|wallycore' requirements.txt > "$RUNNER_TEMP/btcr-reqs.txt"
53+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
54+
- name: Install dependencies (pure-python backend)
55+
if: matrix.backend == 'purepython'
56+
run: |
57+
python -m pip install --upgrade pip
58+
# Also exclude pycryptodome and protobuf so the pure-python AES,
59+
# ChaCha20-Poly1305, and Keccak fallbacks are exercised.
60+
# pycryptodome is the C-backed implementation; protobuf is the
61+
# only remaining optional dependency that cannot be pure-python.
62+
grep -viE 'coincurve|wallycore|pycryptodome|protobuf' requirements.txt > "$RUNNER_TEMP/btcr-reqs.txt"
63+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
3164
- name: Run All Tests
65+
env:
66+
BTCR_BACKEND: ${{ matrix.backend }}
3267
run: |
3368
python run-all-tests.py -vv

.github/workflows/Latest-Run-All-Tests_Full.yml

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,58 @@ jobs:
1919
matrix:
2020
os: [ubuntu-24.04] # Test Ubuntu Only
2121
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] # Test all supported versions of Python
22+
backend: [coincurve, wallycore, purepython] # Test all three secp256k1 backends
2223

2324
steps:
2425
- uses: actions/checkout@v7
2526
- name: Set up Python ${{ matrix.python-version }}
2627
uses: actions/setup-python@v5
2728
with:
2829
python-version: ${{ matrix.python-version }}
29-
- name: Install dependencies
30+
- name: Install dependencies (coincurve backend)
31+
if: matrix.backend == 'coincurve'
3032
run: |
3133
python -m pip install --upgrade pip
32-
pip install coincurve==21.0.0 || pip install coincurve==20.0.0
33-
pip install -r requirements-full.txt
34+
# Install everything except coincurve first (this pulls in wallycore, the
35+
# fallback backend), then try to add coincurve on top. On platforms where
36+
# coincurve cannot be built (e.g. Python 3.14), the job still runs using
37+
# the wallycore fallback instead of failing the install step.
38+
grep -viE 'coincurve' requirements-full.txt > "$RUNNER_TEMP/btcr-reqs.txt"
39+
# On Python 3.14 coincurve has no wheel and cannot build from source, and
40+
# bip-utils (plus the packages that depend on it) hard-require coincurve.
41+
# Exclude that subtree on 3.14 so the install succeeds using the wallycore
42+
# fallback instead of failing on a coincurve source build.
43+
if [ "${{ matrix.python-version }}" = "3.14" ]; then
44+
grep -viE 'bip-utils|py_crypto_hd_wallet|slip10|stellar_sdk' "$RUNNER_TEMP/btcr-reqs.txt" > "$RUNNER_TEMP/btcr-reqs-3.14.txt"
45+
mv "$RUNNER_TEMP/btcr-reqs-3.14.txt" "$RUNNER_TEMP/btcr-reqs.txt"
46+
fi
47+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
48+
sudo apt install python3-bsddb3
49+
# Per-Python-version coincurve pin (matches available prebuilt wheels):
50+
# 3.13 -> only 21.0.0 has a wheel; 3.14 -> no wheel, fall back to wallycore.
51+
case "${{ matrix.python-version }}" in
52+
3.13) pip install coincurve==21.0.0 || true ;;
53+
*) pip install coincurve==21.0.0 || pip install coincurve==20.0.0 || true ;;
54+
esac
55+
- name: Install dependencies (wallycore backend)
56+
if: matrix.backend == 'wallycore'
57+
run: |
58+
python -m pip install --upgrade pip
59+
pip install wallycore
60+
grep -viE 'coincurve|wallycore' requirements-full.txt > "$RUNNER_TEMP/btcr-reqs.txt"
61+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
62+
sudo apt install python3-bsddb3
63+
- name: Install dependencies (pure-python backend)
64+
if: matrix.backend == 'purepython'
65+
run: |
66+
python -m pip install --upgrade pip
67+
# Also exclude pycryptodome and protobuf so the pure-python AES,
68+
# ChaCha20-Poly1305, and Keccak fallbacks are exercised.
69+
grep -viE 'coincurve|wallycore|pycryptodome|protobuf' requirements-full.txt > "$RUNNER_TEMP/btcr-reqs.txt"
70+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
3471
sudo apt install python3-bsddb3
3572
- name: Run All Tests
73+
env:
74+
BTCR_BACKEND: ${{ matrix.backend }}
3675
run: |
3776
python run-all-tests.py -vv

.github/workflows/PoCL-tests-full.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ jobs:
2424
- name: Install dependencies
2525
run: |
2626
python -m pip install --upgrade pip
27-
pip install coincurve==21.0.0 || pip install coincurve==20.0.0
28-
pip install -r requirements-full.txt
27+
pip install coincurve==21.0.0 || pip install coincurve==20.0.0 || true
28+
grep -viE 'coincurve' requirements-full.txt > "$RUNNER_TEMP/btcr-reqs.txt"
29+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
2930
pip install pyopencl
3031
- name: Run tests with PoCL
3132
env:

.github/workflows/Weekly-Run-All-Tests-Full.yml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,26 @@ jobs:
4343
shell: bash
4444
run: |
4545
python -m pip install --upgrade pip
46-
pip install coincurve==21.0.0 || pip install coincurve==20.0.0
47-
pip install -r requirements-full.txt
46+
# Install everything except coincurve first (this pulls in wallycore, the
47+
# fallback backend), then try to add coincurve on top. On platforms where
48+
# coincurve cannot be built (e.g. Python 3.14), the job still runs using
49+
# the wallycore fallback instead of failing the install step.
50+
grep -viE 'coincurve' requirements-full.txt > "$RUNNER_TEMP/btcr-reqs.txt"
51+
# On Python 3.14 coincurve has no wheel and cannot build from source, and
52+
# bip-utils (plus the packages that depend on it) hard-require coincurve.
53+
# Exclude that subtree on 3.14 so the install succeeds using the wallycore
54+
# fallback instead of failing on a coincurve source build.
55+
if [ "${{ matrix.python-version }}" = "3.14" ]; then
56+
grep -viE 'bip-utils|py_crypto_hd_wallet|slip10|stellar_sdk' "$RUNNER_TEMP/btcr-reqs.txt" > "$RUNNER_TEMP/btcr-reqs-3.14.txt"
57+
mv "$RUNNER_TEMP/btcr-reqs-3.14.txt" "$RUNNER_TEMP/btcr-reqs.txt"
58+
fi
59+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
60+
# Per-Python-version coincurve pin (matches available prebuilt wheels):
61+
# 3.13 -> only 21.0.0 has a wheel; 3.14 -> no wheel, fall back to wallycore.
62+
case "${{ matrix.python-version }}" in
63+
3.13) pip install coincurve==21.0.0 || true ;;
64+
*) pip install coincurve==21.0.0 || pip install coincurve==20.0.0 || true ;;
65+
esac
4866
- name: Install green on Windows (workaround for hanging tests on Github Actions)
4967
if: runner.os == 'Windows'
5068
run: pip install green

.github/workflows/Weekly-Run-All-Tests_Base.yml

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ jobs:
1919
matrix:
2020
os: [ubuntu-24.04, windows-latest, macos-latest] # Test all supported operating systems
2121
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] # Test all supported versions of Python
22+
backend: [coincurve]
23+
include:
24+
# Also exercise the wallycore and pure-python secp256k1 backends on Linux
25+
- os: ubuntu-24.04
26+
backend: wallycore
27+
- os: ubuntu-24.04
28+
backend: purepython
2229
exclude:
2330
- os: windows-latest
2431
python-version: '3.13'
@@ -36,15 +43,45 @@ jobs:
3643
brew install autoconf automake libffi libtool pkg-config gnu-sed swig
3744
fi
3845
shell: bash
39-
- name: Install dependencies
46+
- name: Install dependencies (coincurve backend)
47+
if: matrix.backend == 'coincurve'
4048
shell: bash
4149
run: |
4250
python -m pip install --upgrade pip
43-
pip install coincurve==21.0.0 || pip install coincurve==20.0.0
44-
pip install -r requirements.txt
51+
# Install everything except coincurve first (this pulls in wallycore, the
52+
# fallback backend), then try to add coincurve on top. On platforms where
53+
# coincurve cannot be built (e.g. Python 3.14), the job still runs using
54+
# the wallycore fallback instead of failing the install step.
55+
grep -viE 'coincurve' requirements.txt > "$RUNNER_TEMP/btcr-reqs.txt"
56+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
57+
# Per-Python-version coincurve pin (matches available prebuilt wheels):
58+
# 3.13 -> only 21.0.0 has a wheel; 3.14 -> no wheel, fall back to wallycore.
59+
case "${{ matrix.python-version }}" in
60+
3.13) pip install coincurve==21.0.0 || true ;;
61+
*) pip install coincurve==21.0.0 || pip install coincurve==20.0.0 || true ;;
62+
esac
63+
- name: Install dependencies (wallycore backend)
64+
if: matrix.backend == 'wallycore'
65+
shell: bash
66+
run: |
67+
python -m pip install --upgrade pip
68+
pip install wallycore
69+
grep -viE 'coincurve|wallycore' requirements.txt > "$RUNNER_TEMP/btcr-reqs.txt"
70+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
71+
- name: Install dependencies (pure-python backend)
72+
if: matrix.backend == 'purepython'
73+
shell: bash
74+
run: |
75+
python -m pip install --upgrade pip
76+
# Also exclude pycryptodome and protobuf so the pure-python AES,
77+
# ChaCha20-Poly1305, and Keccak fallbacks are exercised.
78+
grep -viE 'coincurve|wallycore|pycryptodome|protobuf' requirements.txt > "$RUNNER_TEMP/btcr-reqs.txt"
79+
pip install -r "$RUNNER_TEMP/btcr-reqs.txt"
4580
- name: Install green on Windows (workaround for hanging tests on Github Actions)
4681
if: runner.os == 'Windows'
4782
run: pip install green
4883
- name: Run All Tests
84+
env:
85+
BTCR_BACKEND: ${{ matrix.backend }}
4986
run: |
5087
python run-all-tests.py -vv

.github/workflows/termux-tests.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ jobs:
5050
- name: Install dependencies
5151
run: |
5252
/entrypoint.sh pkg install -y python git autoconf automake build-essential libtool pkg-config llvm lld rust swig
53-
/entrypoint.sh pip install -r requirements.txt
53+
# Exclude wallycore so only coincurve is attempted here (matching the
54+
# historical Termux setup); coincurve on Termux/aarch64 is a known
55+
# source-build/runtime issue (see ofek/coincurve#189) and is allowed
56+
# to fail on this platform.
57+
/entrypoint.sh bash -c "grep -viE 'wallycore' requirements.txt | pip install -r /dev/stdin"
5458
- name: Run tests
5559
run: /entrypoint.sh python run-all-tests.py -vv
5660

@@ -95,7 +99,7 @@ jobs:
9599
- name: Install dependencies
96100
run: |
97101
/entrypoint.sh pkg install -y python git autoconf automake build-essential libtool pkg-config llvm lld rust swig libsodium
98-
/entrypoint.sh bash -c "export ANDROID_API_LEVEL=24 && export SODIUM_INSTALL=system && pip install maturin --no-binary maturin && pip install py-sr25519-bindings==0.2.3 --no-build-isolation && grep -vi wallycore requirements-full.txt | pip install -r /dev/stdin"
102+
/entrypoint.sh bash -c "export ANDROID_API_LEVEL=24 && export SODIUM_INSTALL=system && pip install maturin --no-binary maturin && pip install py-sr25519-bindings==0.2.3 --no-build-isolation && grep -viE 'wallycore|bip-utils|py_crypto_hd_wallet|slip10|stellar_sdk' requirements-full.txt | pip install -r /dev/stdin"
99103
- name: Install wallycore (optional, may fail to build from source on some platforms)
100104
continue-on-error: true
101105
run: /entrypoint.sh pip install wallycore==1.5.2

benchmark.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
WALLET_DIR = os.path.join(SCRIPT_DIR, "btcrecover", "test", "test-wallets")
5151
RESULTS_DIR = os.path.join(SCRIPT_DIR, "benchmark-results")
5252

53+
# The secp256k1 backends btcrecover.crypto_backends can select between.
54+
BACKENDS = ("coincurve", "wallycore", "purepython")
55+
5356
# Extra seconds (on top of the test duration) allowed for a subprocess to
5457
# reach and finish its measured run. This covers slow one-off setup such as
5558
# first-time OpenCL kernel compilation, which can take a while on integrated
@@ -875,6 +878,9 @@ def _append_opencl_args(cmd, opencl_args):
875878

876879
def run_all_benchmarks(args):
877880
"""Run all configured benchmarks and return results."""
881+
# BTCR_BACKEND is exported by _configure_backend() before we get here, so
882+
# subprocesses inherit the requested backend.
883+
878884
# Build GPU/OpenCL argument dicts from CLI args
879885
gpu_args = {}
880886
opencl_args = {}
@@ -906,6 +912,9 @@ def run_all_benchmarks(args):
906912
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
907913
"duration_per_test_seconds": args.duration,
908914
"threads": threads,
915+
"backend": args.backend or "auto",
916+
# What actually loaded, which is not necessarily what was requested.
917+
"backend_actual": getattr(args, "backend_actual", None) or _get_active_backend(),
909918
"comment": args.comment,
910919
"gpu_args": gpu_args if gpu_args else None,
911920
"opencl_args": opencl_args if opencl_args else None,
@@ -990,6 +999,53 @@ def run_all_benchmarks(args):
990999
return results
9911000

9921001

1002+
def _get_active_backend():
1003+
"""Get the secp256k1 backend btcrecover actually selected.
1004+
1005+
Queried in a subprocess rather than by importing crypto_backends here, so
1006+
that the answer reflects what the benchmark's own worker subprocesses will
1007+
select: same interpreter, same working directory, same BTCR_BACKEND.
1008+
Returns "unknown" if the backend could not be determined.
1009+
"""
1010+
try:
1011+
result = subprocess.run(
1012+
[sys.executable, "-c",
1013+
"from btcrecover import crypto_backends; print(crypto_backends.BACKEND_NAME)"],
1014+
capture_output=True, text=True, timeout=60,
1015+
cwd=SCRIPT_DIR,
1016+
)
1017+
for line in result.stdout.splitlines():
1018+
if line.strip() in BACKENDS:
1019+
return line.strip()
1020+
except Exception:
1021+
pass
1022+
return "unknown"
1023+
1024+
1025+
def _configure_backend(args):
1026+
"""Force the requested backend, then confirm which one really loaded.
1027+
1028+
crypto_backends falls back to the next available backend when a forced one
1029+
cannot be imported, so a requested backend is not proof of what ran. Returns
1030+
the active backend name, or None if it did not match what was requested.
1031+
"""
1032+
if args.backend:
1033+
os.environ["BTCR_BACKEND"] = args.backend
1034+
1035+
active = _get_active_backend()
1036+
1037+
if args.backend and active != args.backend:
1038+
print(f"ERROR: --backend {args.backend} was requested, but btcrecover selected "
1039+
f"'{active}' instead.")
1040+
print(" btcrecover falls back to the next available backend when a forced one")
1041+
print(" cannot be imported, so continuing would benchmark the wrong library and")
1042+
print(f" label the results as '{args.backend}'. Install {args.backend}, or re-run")
1043+
print(f" with --backend {active}.")
1044+
return None
1045+
1046+
return active
1047+
1048+
9931049
def _get_btcrecover_version():
9941050
"""Get the btcrecover version string."""
9951051
try:
@@ -1074,8 +1130,11 @@ def main():
10741130
%(prog)s --wallet-type password Only test password recovery
10751131
%(prog)s --global-ws 8192 GPU benchmarks with custom work size
10761132
%(prog)s --opencl-workgroup-size 1024 OpenCL with custom workgroup
1077-
%(prog)s --comment "GitHub actions run" Add a free-form comment in metadata
1078-
%(prog)s --output results.json Save results to a specific file
1133+
%(prog)s --comment "GitHub actions run" Add a free-form comment in metadata
1134+
%(prog)s --output results.json Save results to a specific file
1135+
%(prog)s --backend coincurve Force a specific secp256k1 backend
1136+
%(prog)s --backend purepython Test with pure-Python secp256k1
1137+
%(prog)s --backend wallycore Test with wallycore secp256k1
10791138
"""
10801139
)
10811140

@@ -1117,6 +1176,12 @@ def main():
11171176
"--comment", type=str, default=None,
11181177
help="Optional free-form note to include in benchmark metadata"
11191178
)
1179+
parser.add_argument(
1180+
"--backend", choices=list(BACKENDS), default=None,
1181+
help="Force a specific secp256k1 backend. Sets the BTCR_BACKEND environment "
1182+
"variable for all subprocesses (default: let btcrecover auto-select). "
1183+
"The run aborts if the requested backend is not the one that actually loads."
1184+
)
11201185
parser.add_argument(
11211186
"--startup-timeout", type=int, default=SEARCH_PHASE_TIMEOUT, metavar="SECONDS",
11221187
help="Extra seconds beyond --duration to allow for subprocess startup / "
@@ -1163,11 +1228,19 @@ def main():
11631228
# Apply the (possibly overridden) startup timeout used by run_benchmark
11641229
SEARCH_PHASE_TIMEOUT = args.startup_timeout
11651230

1231+
# Export BTCR_BACKEND and confirm it took effect before spending time on a run
1232+
# whose results would be mislabeled.
1233+
args.backend_actual = _configure_backend(args)
1234+
if args.backend_actual is None:
1235+
return 1
1236+
11661237
print("BTCRecover Benchmarking Tool")
11671238
print(f"{'=' * 60}")
11681239
print(f"Duration per test: {args.duration} seconds")
11691240
print(f"Wallet types: {args.wallet_type}")
11701241
print(f"Threads: {args.threads if args.threads else 'auto (btcrecover default)'}")
1242+
print(f"Backend: {args.backend_actual}"
1243+
f"{' (forced via --backend)' if args.backend else ' (auto-selected)'}")
11711244
print(f"GPU benchmarks: {'Yes' if args.gpu else 'No'}")
11721245
print(f"OpenCL benchmarks: {'Yes' if args.opencl else 'No'}")
11731246
if args.comment:

0 commit comments

Comments
 (0)