golang/go · error
crypto/ecdh: invalid public key
Error message
crypto/ecdh: invalid public key
What it means
NIST-curve ECDH public keys must be in SEC1 uncompressed form, which begins with the 0x04 prefix byte followed by the x and y coordinates. NewPublicKey rejects empty input and any key whose first byte is not 0x04 (i.e. compressed encodings starting with 0x02/0x03 and the point at infinity) before delegating to BoringCrypto. The comment notes BoringCrypto would otherwise accept these.
Source
Thrown at src/crypto/ecdh/nist.go:124
}
k := &PrivateKey{
curve: c,
privateKey: bytes.Clone(key),
fips: fk,
publicKey: &PublicKey{
curve: c,
publicKey: fk.PublicKey().Bytes(),
fips: fk.PublicKey(),
},
}
return k, nil
}
func (c *nistCurve) NewPublicKey(key []byte) (*PublicKey, error) {
// Reject the point at infinity and compressed encodings.
// Note that boring.NewPublicKeyECDH would accept them.
if len(key) == 0 || key[0] != 4 {
return nil, errors.New("crypto/ecdh: invalid public key")
}
k := &PublicKey{
curve: c,
publicKey: bytes.Clone(key),
}
if boring.Enabled {
bk, err := boring.NewPublicKeyECDH(c.name, k.publicKey)
if err != nil {
return nil, errors.New("crypto/ecdh: invalid public key")
}
k.boring = bk
} else {
fk, err := c.newPublicKey(key)
if err != nil {
return nil, err
}
k.fips = fk
}View on GitHub (pinned to b6b368adc5)
Solutions
- Provide the uncompressed SEC1 encoding: 0x04 || X || Y.
- If you only have a compressed point, decompress it first (e.g. via elliptic.UnmarshalCompressed) then re-encode uncompressed.
- Reject peer keys that do not start with 0x04 at the protocol boundary.
Example fix
// before
pub, err := curve.NewPublicKey(compressedKey) // compressedKey[0] == 0x02
// after
x, y := elliptic.UnmarshalCompressed(curve.Curve, compressedKey)
uncompressed := append([]byte{0x04}, append(x, y...)...)
pub, err := curve.NewPublicKey(uncompressed) Defensive patterns
Strategy: validation
Validate before calling
func loadPub(curve ecdh.Curve, key []byte) (*ecdh.PublicKey, error) {
if len(key) == 0 || key[0] != 0x04 {
return nil, fmt.Errorf("public key must be uncompressed SEC1 (0x04 prefix), got %d bytes", len(key))
}
return curve.NewPublicKey(key)
} Type guard
func isUncompressedSEC1(key []byte) bool {
return len(key) > 0 && key[0] == 0x04
} Try / catch
pub, err := curve.NewPublicKey(key)
if err != nil && len(key) > 0 && key[0] != 0x04 {
// decompress first, then retry with 0x04 || X || Y
} Prevention
- Require uncompressed (0x04) SEC1 encoding at the protocol boundary.
- If peers send compressed points, decompress (elliptic.UnmarshalCompressed) before ECDH.
- Reject empty public-key buffers early.
When it happens
Trigger: Calling curve.NewPublicKey(key) where key is empty, or key[0] is 0x02/0x03 (compressed) or any non-0x04 value.
Common situations: Peer sends a compressed point; receiving an X9.62-encoded key with a different prefix; empty buffer from a failed read; serializing with a library that defaults to compressed form.
Related errors
- crypto/ecdh: private key and public key curves do not match
- crypto/ecdh: invalid private key
- crypto/dsa: invalid public key
- 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/392481a809caa823.
Report an issue: GitHub.