Error message
Unexpected recoverable signature length: Expected 65, but got 64 bytes
Also printed as
- eth_utils.exceptions.ValidationError: Unexpected recoverable signature length: Expected 65, but got 64 bytes
The byte count after "but got" is your signature's length. The exception class is `eth_utils.exceptions.ValidationError`.
web3.py / eth-account / eth-keys (Python) — eth_utils.exceptions.ValidationError from Account.recover_message
What it means
eth-keys (used by eth-account and web3.py) builds a recoverable signature only from exactly 65 bytes: r (32), s (32) and the recovery byte v. The signature you passed has another length — 64 bytes in this case — so recovery could not start. web3.py's `w3.eth.account.recover_message` raises the same error, because it delegates to eth-account.
Why it happens
v was stored separately or dropped
commonBack ends that store `r` and `s` columns, or front ends that slice the signature, lose the last byte. Without v the public key cannot be recovered unambiguously.
An EIP-2098 compact signature
occasional64-byte compact signatures fold the recovery bit into the top bit of s. eth-account does not accept them directly.
Hex text passed as bytes
occasionalPassing `signature_hex.encode()` gives 132 bytes of ASCII instead of 65 bytes of signature. Pass the hex string itself or `bytes.fromhex(...)` without the `0x`.
How to fix it
- 1.
Pass the full signature as returned by the wallet
`personal_sign` / `eth_signTypedData_v4` return a 65-byte hex string; keep it intact end to end.
python from eth_account import Account from eth_account.messages import encode_defunct # Pass the 0x-prefixed hex string exactly as the wallet returned it (132 hex chars + "0x"). signer = Account.recover_message(encode_defunct(text=message), signature=signature_hex) assert signer.lower() == expected_address.lower() - 2.
Expand compact signatures
Split yParity from the top bit of the second half and append v = 27 + yParity.
python def expand_eip2098(sig: bytes) -> bytes: """64-byte compact signature (r || yParityAndS) -> 65-byte r || s || v.""" r, ys = sig[:32], int.from_bytes(sig[32:], "big") y_parity = ys >> 255 s = ys & ((1 << 255) - 1) return r + s.to_bytes(32, "big") + bytes([27 + y_parity])