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 their

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Ensure both keys are produced/loaded via the same Curve instance (e.g. both ecdh.P256()).
  2. Compare curves before calling ECDH: if priv.PublicKey().Curve() != remotePub.Curve() handle the mismatch.
  3. Persist the curve name alongside serialized keys and reconstruct keys through the matching Curve accessor.
  4. 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

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


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