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]byte

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify len(key) == 32 before calling NewPublicKey; if the key is hex or base64 encoded, decode it first with hex.DecodeString or base64.Decode.
  2. 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.
  3. 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

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


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