golang/go · error
crypto/ecdh: invalid public key
Error message
crypto/ecdh: invalid public key
What it means
Thrown by x25519Curve.NewPublicKey when the provided key slice is not exactly 32 bytes (x25519PublicKeySize). X25519 public keys are fixed-length per RFC 7748, so any other length is structurally invalid and cannot represent a point on Curve25519. The check runs after the FIPS-140-only mode gate, so it applies in all non-FIPS usage.
Source
Thrown at src/crypto/ecdh/x25519.go:74
publicKey := make([]byte, x25519PublicKeySize)
x25519Basepoint := [32]byte{9}
x25519ScalarMult(publicKey, key, x25519Basepoint[:])
// We don't check for the all-zero public key here because the scalar is
// never zero because of clamping, and the basepoint is not the identity in
// the prime-order subgroup(s).
return &PrivateKey{
curve: c,
privateKey: bytes.Clone(key),
publicKey: &PublicKey{curve: c, publicKey: publicKey},
}, nil
}
func (c *x25519Curve) NewPublicKey(key []byte) (*PublicKey, error) {
if fips140only.Enforced() {
return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
}
if len(key) != x25519PublicKeySize {
return nil, errors.New("crypto/ecdh: invalid public key")
}
return &PublicKey{
curve: c,
publicKey: bytes.Clone(key),
}, nil
}
func (c *x25519Curve) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error) {
out := make([]byte, x25519SharedSecretSize)
x25519ScalarMult(out, local.privateKey, remote.publicKey)
if isZero(out) {
return nil, errors.New("crypto/ecdh: bad X25519 remote ECDH input: low order point")
}
return out, nil
}
func x25519ScalarMult(dst, scalar, point []byte) {
var e [32]byteView on GitHub (pinned to b6b368adc5)
Solutions
- Verify len(key) == 32 before calling NewPublicKey; if the key is hex or base64 encoded, decode it first with hex.DecodeString or base64.Decode.
- If loading from PEM, use crypto/x509 to parse the key and then extract the raw bytes rather than feeding the entire PEM/DER blob.
- Ensure no extra framing bytes (length prefixes, version tags, algorithm OIDs) are prepended to the raw key material.
Example fix
// before
raw := []byte("MCowBQYDK2VuAyEA...") // base64 string as bytes
pub, err := ecdh.X25519().NewPublicKey(raw)
// after
decoded, err := base64.RawURLEncoding.DecodeString("MCowBQYDK2VuAyEA...")
if err != nil { return err }
pub, err := ecdh.X25519().NewPublicKey(decoded) Defensive patterns
Strategy: validation
Validate before calling
func validateX25519PublicKey(key []byte) error {
if len(key) != 32 {
return fmt.Errorf("x25519 public key must be 32 bytes, got %d", len(key))
}
return nil
}
// call before: ecdh.X25519().NewPublicKey(key) Type guard
func isValidX25519PublicKey(key []byte) bool {
return len(key) == 32
} Try / catch
pub, err := ecdh.X25519().NewPublicKey(key)
if err != nil {
return fmt.Errorf("invalid X25519 public key (len=%d, want 32): %w", len(key), err)
} Prevention
- Always decode hex/base64-encoded keys before passing to NewPublicKey.
- Use crypto/x509 for parsing keys from PEM/DER containers.
- Assert len(key) == 32 in tests when generating or transforming keys.
When it happens
Trigger: Calling ecdh.X25519().NewPublicKey(key) where len(key) != 32. Common triggers: passing a base64/hex-encoded string without decoding, passing a raw elliptic.Unmarshal point, passing a truncated or padded key, or passing an Ed25519 public key (also 32 bytes but semantically different — this won't error but will produce wrong results).
Common situations: Reading an X25519 public key from a PEM/DER file and forgetting to extract the raw 32-byte seed; receiving a key over a network protocol that prepends a length prefix or algorithm identifier byte; copying a key from a hex string without hex.DecodeString; mixing up X25519 and Ed25519 key formats.
Related errors
- crypto/ecdh: invalid private key
- crypto/ecdh: use of X25519 is not allowed in FIPS 140-only m
- crypto/ecdh: invalid private key size
- crypto/ecdh: bad X25519 remote ECDH input: low order point
- ecdsa: private key scalar too large
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/f75003b9b776048c.
Report an issue: GitHub.