Merge pull request #10880 from ekzyis/bip32-reject-xpriv-non-zero-key-prefix
What changed, and why it matters
This update makes Electrum stricter when reading a BIP32 extended private key (the long encoded string that can generate all your wallet's addresses). BIP32 requires that the 33-byte private-key field start with a zero byte before the actual 32-byte secret. Electrum now rejects keys that violate this rule, which closes a gap where a malformed key could be accepted and later produce unexpected public keys or wallet behavior.
Review whether any other xprv/xpub parsing paths bypass this check, and consider whether hardened-derivation or import flows need similar enforcement. No immediate user action is required beyond updating to a version containing this fix.
Security signals we found
Input validation added to BIP32 extended private key parsing
Enforces BIP32 serialization requirement that private key field is 0x00 || ser256(k)
Rejects malformed xprv strings that previously could be accepted
Uses BIP32 test vector 5 invalid cases for regression testing
Evidence from the diff
The patch adds a validation check in BIP32Node.from_xkey(): for xprv inputs, it verifies that xkey[13+32] (the byte immediately preceding the 32-byte private key material) equals 0x00, as required by BIP32. If not, it raises BitcoinException. Tests are added using BIP32 test vector 5 cases with invalid private-key prefixes and a version/prefix mismatch. This prevents parsing of non-conforming extended private keys.
Changed components
electrum/bip32.pytests/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 44/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.