golang/go · error
crypto/ecdh: public key is the identity element
Error message
crypto/ecdh: public key is the identity element
What it means
Thrown by fips140/ecdh.ecdh when len(k.pub.q) == 1, i.e. the LOCAL private key's embedded public point is encoded as the identity element (single 0x00 byte). This implements SP 800-56A Rev. 3 §5.6.2.3.4 Step 1. The peer's validity is checked separately via SetBytes and the later ScalarMult/BytesX; this guard specifically rejects a malformed local key whose public point is the identity.
Source
Thrown at src/crypto/internal/fips140/ecdh/ecdh.go:252
fipsSelfTest()
fips140.RecordApproved()
return ecdh(c, k, peer)
}
func ecdh[P Point[P]](c *Curve[P], k *PrivateKey, peer *PublicKey) ([]byte, error) {
if c.curve != k.pub.curve {
return nil, errors.New("crypto/ecdh: mismatched curves")
}
if k.pub.curve != peer.curve {
return nil, errors.New("crypto/ecdh: mismatched curves")
}
// This applies the Shared Secret Computation of the Ephemeral Unified Model
// scheme specified in NIST SP 800-56A Rev. 3, Section 6.1.2.2.
// Per Section 5.6.2.3.4, Step 1, reject the identity element (0x00).
if len(k.pub.q) == 1 {
return nil, errors.New("crypto/ecdh: public key is the identity element")
}
// SetBytes checks that (x, y) are reduced modulo p, and that they are on
// the curve, performing Steps 2-3 of Section 5.6.2.3.4.
p, err := c.newPoint().SetBytes(peer.q)
if err != nil {
return nil, err
}
// Compute P according to Section 5.7.1.2.
if _, err := p.ScalarMult(p, k.d); err != nil {
return nil, err
}
// BytesX checks that the result is not the identity element, and returns the
// x-coordinate of the result, performing Steps 2-5 of Section 5.7.1.2.
return p.BytesX()
}View on GitHub (pinned to b6b368adc5)
Solutions
- Always construct PrivateKey values through NewPrivateKey (or GenerateKey) so the public point is validated.
- If you deserialize a PrivateKey, re-validate it by re-running NewPrivateKey on its bytes before use.
- Sanity-check that len(priv.pub.q) > 1 before calling ECDH.
Example fix
// before: priv assembled directly, pub.q == []byte{0x00}
secret, err := ecdh.ECDH(curve, malformedPriv, peer)
// after: rebuild the key through the validating constructor
priv, err := ecdh.NewPrivateKey(curve, priv.d) // re-validates public point
if err != nil { return err }
secret, err := ecdh.ECDH(curve, priv, peer) Defensive patterns
Strategy: validation
Validate before calling
// Reject a local private key whose public point is the identity.
if len(priv.PublicKey().Bytes()) <= 1 {
return errors.New("local public key is the identity element")
} Type guard
func localKeyIsValid(priv *ecdh.PrivateKey) bool {
return len(priv.PublicKey().Bytes()) > 1
} Prevention
- Always build PrivateKey via NewPrivateKey/GenerateKey so the public point is validated.
- Re-validate deserialized keys through NewPrivateKey before use.
- Never assemble a PrivateKey struct literal by hand.
When it happens
Trigger: Calling ECDH with a PrivateKey whose pub.q field is the single-byte 0x00 identity encoding. Normally impossible through NewPrivateKey (which rejects such points), so it implies a PrivateKey assembled by hand, deserialized unsafely, or corrupted.
Common situations: Constructing a PrivateKey struct literal or unmarshaling it in a way that bypasses NewPrivateKey validation; memory/serialization corruption of the public point.
Related errors
- crypto/ecdh: invalid private key
- crypto/ecdh: invalid public key
- crypto/ecdh: mismatched curves
- crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-
- crypto/ecdh: use of X25519 is not allowed in FIPS 140-only m
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/bb781abccc3fdd07.
Report an issue: GitHub.