{"record":{"id":"abceccb1da2b3779","repo":"golang/go","slug":"p256-point-not-on-curve","errorCode":null,"errorMessage":"P256 point not on curve","messagePattern":"P256 point not on curve","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/internal/fips140/nistec/p256.go","lineNumber":143,"sourceCode":"\n// p256Polynomial sets y2 to x³ - 3x + b, and returns y2.\nfunc p256Polynomial(y2, x *fiat.P256Element) *fiat.P256Element {\n\ty2.Square(x)\n\ty2.Mul(y2, x)\n\n\tthreeX := new(fiat.P256Element).Add(x, x)\n\tthreeX.Add(threeX, x)\n\ty2.Sub(y2, threeX)\n\n\treturn y2.Add(y2, p256B())\n}\n\nfunc p256CheckOnCurve(x, y *fiat.P256Element) error {\n\t// y² = x³ - 3x + b\n\trhs := p256Polynomial(new(fiat.P256Element), x)\n\tlhs := new(fiat.P256Element).Square(y)\n\tif rhs.Equal(lhs) != 1 {\n\t\treturn errors.New(\"P256 point not on curve\")\n\t}\n\treturn nil\n}\n\n// Bytes returns the uncompressed or infinity encoding of p, as specified in\n// SEC 1, Version 2.0, Section 2.3.3. Note that the encoding of the point at\n// infinity is shorter than all other encodings.\nfunc (p *P256Point) Bytes() []byte {\n\t// This function is outlined to make the allocations inline in the caller\n\t// rather than happen on the heap.\n\tvar out [p256UncompressedLength]byte\n\treturn p.bytes(&out)\n}\n\nfunc (p *P256Point) bytes(out *[p256UncompressedLength]byte) []byte {\n\t// The SEC 1 representation of the point at infinity is a single zero byte,\n\t// and only infinity has z = 0.\n\tif p.z.IsZero() == 1 {","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/internal/fips140/nistec/p256.go#L125-L161","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\n// (x, y) hand-extracted, then checked manually\n// after (route through validated parser)\np, err := nistec.NewP256Point().SetBytes(sec1)\nif err != nil { return fmt.Errorf(\"invalid P-256 point: %w\", err) }\n// no separate CheckOnCurve needed; SetBytes already enforces it","handlingStrategy":"try-catch","validationCode":"// Prefer the validated parser; CheckOnCurve is rarely needed standalone.\np, err := nistec.NewP256Point().SetBytes(sec1)\nif err != nil { return err }","typeGuard":"func isValidP256(sec1 []byte) bool {\n    _, err := nistec.NewP256Point().SetBytes(sec1)\n    return err == nil\n}","tryCatchPattern":"if err := p256CheckOnCurve(x, y); err != nil {\n    return fmt.Errorf(\"P-256 on-curve check failed: %w\", err)\n}","preventionTips":["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."],"tags":["elliptic-curve","fips140","crypto","nistec","p256","point-validation"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}