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

  1. Always construct PrivateKey values through NewPrivateKey (or GenerateKey) so the public point is validated.
  2. If you deserialize a PrivateKey, re-validate it by re-running NewPrivateKey on its bytes before use.
  3. 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

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


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