golang/go · error
crypto/ecdh: private key and public key curves do not match
Error message
crypto/ecdh: private key and public key curves do not match
What it means
ECDH key agreement requires both parties' keys to be on the same curve. PrivateKey and PublicKey each carry an opaque, comparable curve field; if k.curve != remote.curve the operation is refused before any scalar multiplication, because cross-curve ECDH is undefined.
Source
Thrown at src/crypto/ecdh/ecdh.go:133
publicKey *PublicKey
boring *boring.PrivateKeyECDH
fips *ecdh.PrivateKey
}
// ECDH performs an ECDH exchange and returns the shared secret. The [PrivateKey]
// and [PublicKey] must use the same curve.
//
// For NIST curves, this performs ECDH as specified in SEC 1, Version 2.0,
// Section 3.3.1, and returns the x-coordinate encoded according to SEC 1,
// Version 2.0, Section 2.3.5. The result is never the point at infinity.
// This is also known as the Shared Secret Computation of the Ephemeral Unified
// Model scheme specified in NIST SP 800-56A Rev. 3, Section 6.1.2.2.
//
// For [X25519], this performs ECDH as specified in RFC 7748, Section 6.1. If
// the result is the all-zero value, ECDH returns an error.
func (k *PrivateKey) ECDH(remote *PublicKey) ([]byte, error) {
if k.curve != remote.curve {
return nil, errors.New("crypto/ecdh: private key and public key curves do not match")
}
return k.curve.ecdh(k, remote)
}
// Bytes returns a copy of the encoding of the private key.
func (k *PrivateKey) Bytes() []byte {
// Copy the private key to a fixed size buffer that can get allocated on the
// caller's stack after inlining.
var buf [66]byte
return append(buf[:0], k.privateKey...)
}
// Equal returns whether x represents the same private key as k.
//
// Note that there can be equivalent private keys with different encodings which
// would return false from this check but behave the same way as inputs to [ECDH].
//
// This check is performed in constant time as long as the key types and theirView on GitHub (pinned to b6b368adc5)
Solutions
- Ensure both keys are produced/loaded via the same Curve instance (e.g. both ecdh.P256()).
- Compare curves before calling ECDH: if priv.PublicKey().Curve() != remotePub.Curve() handle the mismatch.
- Persist the curve name alongside serialized keys and reconstruct keys through the matching Curve accessor.
- Negotiate a single curve up front in the protocol and reject peer keys for other curves.
Example fix
// before secret, err := privP256.ECDH(pubP384) // different curves // after secret, err := privP256.ECDH(pubP256) // same curve
Defensive patterns
Strategy: type-guard
Validate before calling
func ecdhOrErr(priv *ecdh.PrivateKey, remote *ecdh.PublicKey) ([]byte, error) {
if !sameCurve(priv, remote) {
return nil, fmt.Errorf("curve mismatch: local=%s remote=%s",
priv.Curve(), remote.Curve())
}
return priv.ECDH(remote)
} Type guard
func sameCurve(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) bool {
return priv != nil && pub != nil &&
priv.PublicKey().Curve() == pub.Curve()
} Try / catch
secret, err := priv.ECDH(remote)
if err != nil {
if errors.Is(err, errCurveMismatch) /* compare via message if needed */ {
// negotiate the correct curve and retry with a fresh peer key
}
return err
} Prevention
- Store the curve name with serialized keys and reconstruct via the matching Curve accessor.
- Negotiate a single curve in the protocol and reject peer keys for other curves.
- Unit-test ECDH with mismatched curves to confirm it errors cleanly.
When it happens
Trigger: Calling priv.ECDH(remotePub) where priv was created with e.g. ecdh.P256() and remotePub with ecdh.P384(), or where one side is an X25519 key and the other a NIST key.
Common situations: Mixing key pairs of different curves in a handshake; deserializing a public key without recording which curve it belongs to; configuration drift between client and server curve selection; peer sends a key for a curve you did not negotiate.
Related errors
- crypto/ecdh: invalid private key
- crypto/ecdh: invalid public 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/25bae841ec9e4745.
Report an issue: GitHub.