bip32: reject extended private key with non-zero key prefix
What changed, and why it matters
Electrum added a check to reject malformed BIP32 extended private keys ('xprv...') where the private key data does not start with a required zero byte. Previously, such invalid keys may have been accepted. This is a defensive validation fix that prevents non-standard or corrupted keys from being processed, reducing the risk of key-handling bugs or unexpected behavior.
Review whether any other xkey parsing paths (imports, sweeping, wallet restoration) bypass from_xkey() and ensure consistent validation. Consider whether invalid keys accepted in older versions could have produced persistently wrong derived keys or addresses, and assess if user-facing warnings are needed for restored wallets created from such keys.
Security signals we found
Input validation added for BIP32 extended private key format
Rejects non-zero prefix before serialized private key per BIP32 spec
Uses existing BitcoinException for malformed key handling
Tests reference BIP32 test vector 5 invalid cases
Evidence from the diff
In electrum/bip32.py, from_xkey() now verifies that for private extended keys, the byte at position 45 (13 bytes prefix + 32 bytes parent fingerprint/child number/chain code) is 0x00, as required by BIP32 (0x00 || ser256(k)). If not, it raises BitcoinException. Tests were added using BIP32 test vector 5 invalid private key prefixes. The change is a strictness improvement, not a full vulnerability fix with a known exploit.
Changed components
electrum/bip32.pyBIP32Node.from_xkey()tests/test_bitcoin.pyInspect captured patch +16 / −0
### electrum/bip32.py
@@ -155,6 +155,9 @@ def from_xkey(
if not allow_custom_headers and xtype != "standard":
raise ValueError(f"only standard xpub/xprv allowed. found custom xtype={xtype}")
if is_private:
+ if xkey[13 + 32] != 0:
+ raise BitcoinException('Invalid extended private key: '
+ 'key data must be prefixed with 0x00')
eckey = ecc.ECPrivkey(xkey[13 + 33:])
else:
eckey = ecc.ECPubkey(xkey[13 + 32:])
### tests/test_bitcoin.py
@@ -763,6 +763,19 @@ def test_bip32_from_xkey(self):
self.assertEqual(bytes.fromhex("03f18e53f3386a5f9a9d2c369ad3b84b429eb397b4bc69ce600f2d833b54ba32f4"),
bip32node2.eckey.get_public_key_bytes(compressed=True))
+ def test_bip32_from_xkey_private_key_bad_prefix(self):
+ # A private extended key's key field must be 0x00 || ser256(k).
+ for invalid in (
+ # BIP32 test vector 5: prvkey version / pubkey mismatch
+ "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH"
+ # BIP32 test vector 5: invalid prvkey prefix 04
+ "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ",
+ # BIP32 test vector 5: invalid prvkey prefix 01
+ "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J",
+ ):
+ with self.assertRaises(BitcoinException):
+ BIP32Node.from_xkey(invalid)
+
def test_is_bip32_derivation(self):
self.assertTrue(is_bip32_derivation("m/0'/1"))
self.assertTrue(is_bip32_derivation("m/0'/0'"))Why this scored 59/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.