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

  1. Confirm coordinates come from the P-256 curve specifically.
  2. Re-obtain the point from a trusted SEC1 encoding via SetBytes (which enforces on-curve).
  3. For untrusted public keys, run CheckOnCurve and reject on error; do not repair.
  4. 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

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


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/abceccb1da2b3779. Report an issue: GitHub.