golang/go · error

crypto/ecdh: invalid public key

Error message

crypto/ecdh: invalid public key

What it means

Thrown by fips140/ecdh.NewPublicKey when the key is empty or its first byte is not 0x04. This package only accepts the uncompressed point encoding (prefix 0x04); it explicitly rejects the point-at-infinity, compressed encodings (0x02/0x03), and any malformed leading byte. Subsequent SetBytes then validates that (x,y) are in [0,p-1] and on the curve.

Source

Thrown at src/crypto/internal/fips140/ecdh/ecdh.go:219

		panic("crypto/ecdh: internal error: nistec ScalarBaseMult failed for a fixed-size input")
	}

	publicKey := p.Bytes()
	if len(publicKey) == 1 {
		// The encoding of the identity is a single 0x00 byte. This is
		// unreachable because the only scalar that generates the identity is
		// zero, which is rejected above.
		panic("crypto/ecdh: internal error: public key is the identity element")
	}

	k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}}
	return k, nil
}

func NewPublicKey[P Point[P]](c *Curve[P], key []byte) (*PublicKey, error) {
	// Reject the point at infinity and compressed encodings.
	if len(key) == 0 || key[0] != 4 {
		return nil, errors.New("crypto/ecdh: invalid public key")
	}

	// SetBytes checks that x and y are in the interval [0, p - 1], and that
	// the point is on the curve. Along with the rejection of the point at
	// infinity (the identity element) above, this fulfills the requirements
	// of NIST SP 800-56A Rev. 3, Section 5.6.2.3.4.
	if _, err := c.newPoint().SetBytes(key); err != nil {
		return nil, err
	}

	return &PublicKey{curve: c.curve, q: bytes.Clone(key)}, nil
}

func ECDH[P Point[P]](c *Curve[P], k *PrivateKey, peer *PublicKey) ([]byte, error) {
	fipsSelfTest()
	fips140.RecordApproved()
	return ecdh(c, k, peer)
}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Convert the point to uncompressed (0x04 || X || Y) form before passing it in.
  2. Validate len(key) > 0 and key[0] == 4 before calling NewPublicKey.
  3. If the source genuinely uses compressed points, decompress them on the curve first.

Example fix

// before
pub, err := ecdh.NewPublicKey(curve, compressed) // compressed[0] == 0x02 or 0x03

// after: supply uncompressed encoding
uncompressed := append([]byte{0x04}, append(x, y...)...)
pub, err := ecdh.NewPublicKey(curve, uncompressed)
Defensive patterns

Strategy: validation

Validate before calling

// Require uncompressed (0x04) encoding before NewPublicKey.
if len(key) == 0 || key[0] != 4 {
    return errors.New("ecdh public key must be uncompressed (0x04 prefix)")
}
return ecdh.NewPublicKey(curve, key)

Type guard

func isUncompressedPoint(key []byte) bool {
    return len(key) > 0 && key[0] == 0x04
}

Prevention

When it happens

Trigger: Constructing an ECDH public key from a compressed encoding, an empty slice, or a buffer that does not begin with the 0x04 uncompressed prefix.

Common situations: Interoperating with libraries or wire formats (TLS, COSE, JWT) that use compressed points; parsing a malformed/corrupted key; feeding a raw coordinate pair without the prefix.

Related errors


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