golang/go · critical
crypto/ecdh: bad X25519 remote ECDH input: low order point
Error message
crypto/ecdh: bad X25519 remote ECDH input: low order point
What it means
Thrown by x25519Curve.ecdh after computing the shared secret via x25519ScalarMult. If the output is all zeros (isZero(out)), the remote public key was a low-order point — a well-known malicious or degenerate input that forces the shared secret to zero. This is a critical security check that prevents small-subgroup and all-zero output attacks per RFC 7748 Section 6.1.
Source
Thrown at src/crypto/ecdh/x25519.go:86
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]byte
copy(e[:], scalar[:])
e[0] &= 248
e[31] &= 127
e[31] |= 64
var x1, x2, z2, x3, z3, tmp0, tmp1 field.Element
x1.SetBytes(point[:])
x2.One()
x3.Set(&x1)
z3.One()
View on GitHub (pinned to b6b368adc5)
Solutions
- Treat this as a security-critical failure: abort the key exchange and close the connection — never proceed with a zero shared secret.
- Validate the remote public key provenance (TLS certificate chain, signed key exchange messages) before calling ECDH to prevent adversarial inputs.
- If this occurs during testing, replace placeholder zero-byte keys with keys generated by ecdh.X25519().GenerateKey().
Example fix
// before
secret, err := priv.ECDH(remotePub)
if err != nil { log.Println(err); return err }
// after
secret, err := priv.ECDH(remotePub)
if err != nil {
// low-order point — abort key exchange, do not retry
return fmt.Errorf("key exchange failed (possible attack): %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
func isLikelyLowOrderPoint(pub *ecdh.PublicKey) bool {
// X25519 all-zero key is the most common low-order point
for _, b := range pub.Bytes() {
if b != 0 { return false }
}
return true
} Try / catch
secret, err := priv.ECDH(remotePub)
if err != nil {
// Abort immediately — do not retry or fall back.
// This may indicate an adversarial peer.
return fmt.Errorf("ECDH failed, possible low-order attack: %w", err)
} Prevention
- Treat ECDH errors as security-critical: never proceed with a failed key exchange.
- Authenticate the peer's public key (e.g., via TLS cert pinning or signed key exchange) before ECDH.
- Never use placeholder zero-byte keys in production code paths.
When it happens
Trigger: Calling ECDH on a PrivateKey with a remote PublicKey that is a low-order point (e.g., all-zero bytes, or one of the few known small-subgroup points on the Montgomery curve). This typically arises when the peer sends a crafted or corrupted public key, or when an attacker injects an all-zero key to force a predictable shared secret.
Common situations: A TLS or Noise protocol peer sends a malformed or adversarial X25519 public key; a key exchange where the remote side has a bug producing zero-filled buffers; testing with placeholder/zero keys; a man-in-the-middle injecting a degenerate point to force a known shared secret.
Related errors
- crypto/ecdh: use of X25519 is not allowed in FIPS 140-only m
- crypto/ecdh: private key and public key curves do not match
- crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-
- crypto/ecdh: invalid private key
- crypto/ecdh: invalid public key
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/52f0398efbd3bfa1.
Report an issue: GitHub.