What changed, and why it matters
This commit fixes a bug in how Electrum parsed Bitcoin wallet derivation paths typed by users. A path like "m/2147483648" was silently treated the same as "m/0'" (a hardened key), and "m/2147483649'" lost its hardening marker. The patch now rejects any index of 2^31 or larger and tells the user to use the standard hardened suffix instead. This prevents confusion and makes path conversion unambiguous, but it is a correctness/hardening fix rather than a demonstrated remote exploit.
Treat as a low-severity hardening fix. Review any code that calls convert_bip32_strpath_to_intpath to ensure the new ValueError is handled gracefully in UI and command-line flows, and consider whether user-facing error messages need localization.
Security signals we found
Input validation gap in BIP32 path parser
Silent reinterpretation of user-supplied index as hardened bit
Ambiguous string-to-integer round-trip for derivation paths
Boundary-condition hardening for cryptographic key derivation input
Evidence from the diff
convert_bip32_strpath_to_intpath previously computed child_index = abs(x_int) | prime and only checked the combined value against UINT32_MAX. Because BIP32_PRIME (0x80000000) is the hardened bit, a literal index in [2^31, 2^32) would set that bit implicitly, while an explicit hardened marker on such an index would be a no-op. The patch validates abs(x_int) < BIP32_PRIME before OR-ing with prime, raising ValueError otherwise. Tests are added for boundary values 2147483647/2147483647’ and rejection of 2147483648, 2147483648’, -2147483648, and 4294967296.
Changed components
electrum/bip32.py::convert_bip32_strpath_to_intpathtests/test_bitcoin.pyInspect captured patch +20 / −3
### electrum/bip32.py
@@ -353,9 +353,12 @@ def convert_bip32_strpath_to_intpath(n: str) -> List[int]:
x_int = int(x)
except ValueError as e:
raise ValueError(f"failed to parse bip32 path: {(str(e))}") from None
- child_index = abs(x_int) | prime
- if child_index > UINT32_MAX:
- raise ValueError(f"bip32 path child index too large: {child_index} > {UINT32_MAX}")
+ x_int = abs(x_int)
+ 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
+ raise ValueError(f"bip32 path child index too large: {x_int} >= {BIP32_PRIME}.")
+ child_index = x_int | prime
path.append(child_index)
return path
### tests/test_bitcoin.py
@@ -814,6 +814,20 @@ 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"))
+ # 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'"))
+ self.assertEqual([0xffffffff], convert_bip32_strpath_to_intpath("m/-2147483647"))
+ # literal index >= 2**31 must be rejected: it would either silently become
+ # hardened ("m/2147483648" == "m/0'") or make the hardened marker a no-op
+ with self.assertRaisesRegex(ValueError, r"^bip32 path child index too large: 2147483648 >= 2147483648\.$"):
+ convert_bip32_strpath_to_intpath("m/2147483648")
+ with self.assertRaisesRegex(ValueError, r"^bip32 path child index too large: 2147483648 >= 2147483648\.$"):
+ convert_bip32_strpath_to_intpath("m/2147483648'")
+ with self.assertRaisesRegex(ValueError, r"^bip32 path child index too large: 2147483648 >= 2147483648\.$"):
+ 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")
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 49/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.