golang/go · error
P256 point not on curve
Error message
P256 point not on curve
What it means
Concrete P-256 instance of the generated on-curve check (generate.go:271 expanded into p256.go:143). Thrown by p256CheckOnCurve when, for a candidate affine (x,y), y^2 != x^3 - 3x + b over the P-256 prime field. The (x,y) pair is not a valid P-256 group point.
Source
Thrown at src/crypto/internal/fips140/nistec/p256.go:143
// p256Polynomial sets y2 to x³ - 3x + b, and returns y2.
func p256Polynomial(y2, x *fiat.P256Element) *fiat.P256Element {
y2.Square(x)
y2.Mul(y2, x)
threeX := new(fiat.P256Element).Add(x, x)
threeX.Add(threeX, x)
y2.Sub(y2, threeX)
return y2.Add(y2, p256B())
}
func p256CheckOnCurve(x, y *fiat.P256Element) error {
// y² = x³ - 3x + b
rhs := p256Polynomial(new(fiat.P256Element), x)
lhs := new(fiat.P256Element).Square(y)
if rhs.Equal(lhs) != 1 {
return errors.New("P256 point not on curve")
}
return nil
}
// Bytes returns the uncompressed or infinity encoding of p, as specified in
// SEC 1, Version 2.0, Section 2.3.3. Note that the encoding of the point at
// infinity is shorter than all other encodings.
func (p *P256Point) Bytes() []byte {
// This function is outlined to make the allocations inline in the caller
// rather than happen on the heap.
var out [p256UncompressedLength]byte
return p.bytes(&out)
}
func (p *P256Point) bytes(out *[p256UncompressedLength]byte) []byte {
// The SEC 1 representation of the point at infinity is a single zero byte,
// and only infinity has z = 0.
if p.z.IsZero() == 1 {View on GitHub (pinned to b6b368adc5)
Solutions
- Confirm coordinates come from the P-256 curve specifically.
- Re-obtain the point from a trusted SEC1 encoding via SetBytes (which enforces on-curve).
- For untrusted public keys, run CheckOnCurve and reject on error; do not repair.
- Prefer the curve's own Add/ScalarMult over hand-rolled field ops so invariants are maintained.
Example fix
// before
// (x, y) hand-extracted, then checked manually
// after (route through validated parser)
p, err := nistec.NewP256Point().SetBytes(sec1)
if err != nil { return fmt.Errorf("invalid P-256 point: %w", err) }
// no separate CheckOnCurve needed; SetBytes already enforces it Defensive patterns
Strategy: try-catch
Validate before calling
// Prefer the validated parser; CheckOnCurve is rarely needed standalone.
p, err := nistec.NewP256Point().SetBytes(sec1)
if err != nil { return err } Type guard
func isValidP256(sec1 []byte) bool {
_, err := nistec.NewP256Point().SetBytes(sec1)
return err == nil
} Try / catch
if err := p256CheckOnCurve(x, y); err != nil {
return fmt.Errorf("P-256 on-curve check failed: %w", err)
} Prevention
- Route points through SetBytes which already enforces on-curve for P-256.
- Confirm coordinates are P-256 specifically, not another NIST curve.
- Use the curve's own Add/ScalarMult for point math to maintain invariants.
When it happens
Trigger: An (x,y) with one coordinate corrupted, coordinates from a different curve, or a y reconstructed incorrectly from a compressed point. Typically surfaced via explicit verification rather than SetBytes (which already validates).
Common situations: Cross-curve confusion (P-384 pair checked against P-256), a tampered public key, custom point arithmetic that bypassed curve invariants, or test vectors with a transposed coordinate.
Related errors
- {{.P}} point not on curve
- invalid P256 compressed point encoding
- invalid P256 point encoding
- invalid {{ .Element }} encoding
- invalid {{.P}} compressed point encoding
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/abceccb1da2b3779.
Report an issue: GitHub.