Error message
Invalid EC key. Point is not on the curve specified.
Reproduced on cryptography 50.0. Other stacks phrase the same failure differently — OpenSSL's error stack says "point is not on curve", Node rejects the JWK with "Invalid JWK EC key" — the mathematics behind all of them is identical.
Python cryptography — ValueError from EllipticCurvePublicNumbers.public_key()
What it means
The (x, y) pair you supplied does not satisfy the curve equation y² = x³ + ax + b (mod p) for the curve you named. Libraries validate this on import because accepting off-curve points enables invalid-curve attacks that can leak private keys — so the rejection is a safety check, not pedantry. The check is one cheap evaluation of the curve equation, and every serious library performs it: an off-curve point will be rejected somewhere, and better here, loudly, than downstream.
Why it happens
Coordinates paired with the wrong curve
commonA secp256k1 point (Bitcoin/Ethereum) declared as SECP256R1/P-256 — or vice versa. Both curves use 32-byte coordinates, so nothing else catches the swap; the curve equation does.
Corrupted or truncated coordinate bytes
commonA stripped leading zero (31-byte coordinate), hex decoded with an off-by-one, base64 vs base64url confusion, or a single flipped bit — any change to x or y almost surely leaves the curve.
Wrong byte order
occasionalCoordinates are big-endian in every standard encoding. Bytes reversed to little-endian (common when interfacing with hardware or hand-rolled code) describe a completely different — off-curve — point.
How to fix it
- 1.
Build the key from the encoded point, with the right curve
from_encoded_point validates length and structure in one step; try the other candidate curve if your source is ambiguous.
python from cryptography.hazmat.primitives.asymmetric import ec point = b"\x04" + x_bytes + y_bytes # uncompressed, 65 bytes for P-256 key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), point) # if that raises, test: ec.SECP256K1() — the other 256-bit suspect - 2.
Check the raw ingredients
Confirm 32-byte lengths and plausible values before constructing; restore stripped zeros by left-padding.
python print(len(x_bytes), len(y_bytes)) # must be 32, 32 for P-256 x_bytes = x_bytes.rjust(32, b"\x00") # repair stripped leading zeros y_bytes = y_bytes.rjust(32, b"\x00")