{"record":{"id":"401bd22bd58b3c34","repo":"golang/go","slug":"point-not-on-curve","errorCode":null,"errorMessage":"point not on curve","messagePattern":"point not on curve","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/internal/boring/ecdh.go","lineNumber":58,"sourceCode":"\n\tnid, err := curveNID(curve)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroup := C._goboringcrypto_EC_GROUP_new_by_curve_name(nid)\n\tif group == nil {\n\t\treturn nil, fail(\"EC_GROUP_new_by_curve_name\")\n\t}\n\tdefer C._goboringcrypto_EC_GROUP_free(group)\n\tkey := C._goboringcrypto_EC_POINT_new(group)\n\tif key == nil {\n\t\treturn nil, fail(\"EC_POINT_new\")\n\t}\n\tok := C._goboringcrypto_EC_POINT_oct2point(group, key, (*C.uint8_t)(unsafe.Pointer(&bytes[0])), C.size_t(len(bytes)), nil) != 0\n\tif !ok {\n\t\tC._goboringcrypto_EC_POINT_free(key)\n\t\treturn nil, errors.New(\"point not on curve\")\n\t}\n\n\tk := &PublicKeyECDH{curve, key, append([]byte(nil), bytes...)}\n\t// Note: Because of the finalizer, any time k.key is passed to cgo,\n\t// that call must be followed by a call to runtime.KeepAlive(k),\n\t// to make sure k is not collected (and finalized) before the cgo\n\t// call returns.\n\truntime.SetFinalizer(k, (*PublicKeyECDH).finalize)\n\treturn k, nil\n}\n\nfunc (k *PublicKeyECDH) Bytes() []byte { return k.bytes }\n\nfunc NewPrivateKeyECDH(curve string, bytes []byte) (*PrivateKeyECDH, error) {\n\tif len(bytes) != curveSize(curve) {\n\t\treturn nil, errors.New(\"NewPrivateKeyECDH: wrong key length\")\n\t}\n","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/internal/boring/ecdh.go#L40-L76","documentation":"Raised by boring.NewPublicKeyECDH when BoringCrypto's EC_POINT_oct2point rejects the bytes — the coordinates do not satisfy the curve equation, so the bytes do not encode a valid point. This is distinct from a length error (290): length is correct but the point is mathematically invalid.","triggerScenarios":"Calling NewPublicKeyECDH with bytes of correct length but whose X,Y do not lie on the curve: corrupted coordinates, adversarially crafted points, or random data of the right size.","commonSituations":"Tampered public keys in transit; malicious peer sending an invalid point (classic ECDH point-validation attack); bit errors; deserialization from untrusted input without validation.","solutions":["Validate/treat this as untrusted input: log and reject the peer, do not attempt correction.","Ensure the bytes came from a peer's genuine ecdh.PublicKey.Bytes() output.","If generating keys, use ecdh.GenerateKey which always yields valid points."],"exampleFix":"// before\npub, err := boring.NewPublicKeyECDH(\"P-256\", attackerBytes) // length ok, point invalid\n\n// after\nif _, err := boring.NewPublicKeyECDH(\"P-256\", peerBytes); err != nil {\n    return errors.New(\"reject peer: invalid ECDH point\")\n}","handlingStrategy":"try-catch","validationCode":"// No pre-check can fully validate on-curve status cheaply; rely on the API.\n// But you can reject obvious garbage:\nfunc maybeValidPoint(b []byte) bool { return len(b) > 0 && b[0] == 0x04 }","typeGuard":null,"tryCatchPattern":"if _, err := boring.NewPublicKeyECDH(curve, peerBytes); err != nil {\n    if err.Error() == \"point not on curve\" {\n        // untrusted peer: reject and log, do not fall back\n        return ErrUntrustedPeer\n    }\n    return err\n}","preventionTips":["Treat public keys from untrusted peers as hostile; let the API validate them.","Never skip point validation in ECDH code paths.","Prefer ecdh.PublicKey.Bytes() round-trips for your own keys."],"tags":["crypto","ecdh","boringcrypto","fips","security","validation"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}