Merge pull request #11000 from f321x/bip32_index_with_whitespace
What changed, and why it matters
This commit tightens how Electrum parses BIP32 wallet derivation paths. Previously, the code used Python's built-in int() conversion, which accepts a wide variety of number-like strings, including whitespace, plus signs, underscores, and non-ASCII digits. The change now requires each path index to be plain ASCII digits only, optionally preceded by a minus sign for hardened derivation. This prevents subtle parsing surprises where a user or attacker could supply a path that looks valid but is interpreted differently than expected.
Review whether any other path-parsing or integer-input functions in the codebase still use raw int() on user-supplied strings and apply similar strict validation. Ensure downstream callers handle the new ValueError correctly.
Security signals we found
Input validation hardening for BIP32 derivation path parsing
Removal of permissive int() parsing that accepted whitespace, plus signs, underscores, and non-ASCII digits
Prevention of ambiguous or surprising path index interpretation
Follow-up to a prior related fix (#10999)
Evidence from the diff
The patch modifies convert_bip32_strpath_to_intpath in electrum/bip32.py. Before, it stripped a leading ‘-’ for hardened notation and then called int(x), which in Python 3 accepts strings such as ’ 5’, ‘+5’, ‘1_000’, and Unicode decimal digits like Arabic-Indic ‘٤٤’. It also took abs(x_int), which could silently convert negative hardened markers. The new code strips the optional ‘-’ first, then validates the remainder with x.isascii() and x.isdecimal() before calling int(x). Tests are added for rejected inputs and for ‘m/-0’ being accepted as hardened zero.
Changed components
electrum/bip32.pyconvert_bip32_strpath_to_intpath functionBIP32 derivation path parsingInspect captured patch +16 / −5
### electrum/bip32.py
@@ -348,12 +348,11 @@ def convert_bip32_strpath_to_intpath(n: str) -> List[int]:
if x.startswith('-'):
if prime:
raise ValueError(f"bip32 path child index is signalling hardened level in multiple ways")
+ x = x[1:]
prime = BIP32_PRIME
- try:
- x_int = int(x)
- except ValueError as e:
- raise ValueError(f"failed to parse bip32 path: {(str(e))}") from None
- x_int = abs(x_int)
+ if not (x.isascii() and x.isdecimal()):
+ raise ValueError(f"failed to parse bip32 path: invalid child index: {x!r}")
+ x_int = int(x)
if x_int >= BIP32_PRIME:
# the top bit is the hardened flag; a literal index >= 2**31 would either
# silently become hardened or make the explicit hardened marker a no-op
### tests/test_bitcoin.py
@@ -814,6 +814,7 @@ def test_convert_bip32_strpath_to_intpath(self):
self.assertEqual([0, 0x80000001, 0x80000001], convert_bip32_strpath_to_intpath("m/0/-1/1'"))
self.assertEqual([], convert_bip32_strpath_to_intpath("m/"))
self.assertEqual([2147483692, 2147488889, 221], convert_bip32_strpath_to_intpath("m/44'/5241h/221"))
+ self.assertEqual([0x80000000], convert_bip32_strpath_to_intpath("m/-0"))
# largest valid non-hardened / hardened index
self.assertEqual([0x7fffffff], convert_bip32_strpath_to_intpath("m/2147483647"))
self.assertEqual([0xffffffff], convert_bip32_strpath_to_intpath("m/2147483647'"))
@@ -828,6 +829,17 @@ def test_convert_bip32_strpath_to_intpath(self):
convert_bip32_strpath_to_intpath("m/-2147483648")
with self.assertRaisesRegex(ValueError, r"^bip32 path child index too large: 4294967296 >= 2147483648\.$"):
convert_bip32_strpath_to_intpath("m/4294967296")
+ # child index must be ascii digits, optionally prefixed with '-'.
+ for path in (
+ "m/ -5",
+ "m/ -5'",
+ "m/5 ",
+ "m/+5",
+ "m/1_000",
+ "m/٤٤'", # arabic-indic digits
+ ):
+ with self.assertRaisesRegex(ValueError, r"^failed to parse bip32 path", msg=path):
+ convert_bip32_strpath_to_intpath(path)
def test_convert_bip32_intpath_to_strpath(self):
self.assertEqual("m/0/1h/1h", convert_bip32_intpath_to_strpath([0, 0x80000001, 0x80000001]))Why this scored 39/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.