golang/go · error

crypto/ecdh: invalid public key

Error message

crypto/ecdh: invalid public key

What it means

NIST-curve ECDH public keys must be in SEC1 uncompressed form, which begins with the 0x04 prefix byte followed by the x and y coordinates. NewPublicKey rejects empty input and any key whose first byte is not 0x04 (i.e. compressed encodings starting with 0x02/0x03 and the point at infinity) before delegating to BoringCrypto. The comment notes BoringCrypto would otherwise accept these.

Source

Thrown at src/crypto/ecdh/nist.go:124

	}
	k := &PrivateKey{
		curve:      c,
		privateKey: bytes.Clone(key),
		fips:       fk,
		publicKey: &PublicKey{
			curve:     c,
			publicKey: fk.PublicKey().Bytes(),
			fips:      fk.PublicKey(),
		},
	}
	return k, nil
}

func (c *nistCurve) NewPublicKey(key []byte) (*PublicKey, error) {
	// Reject the point at infinity and compressed encodings.
	// Note that boring.NewPublicKeyECDH would accept them.
	if len(key) == 0 || key[0] != 4 {
		return nil, errors.New("crypto/ecdh: invalid public key")
	}
	k := &PublicKey{
		curve:     c,
		publicKey: bytes.Clone(key),
	}
	if boring.Enabled {
		bk, err := boring.NewPublicKeyECDH(c.name, k.publicKey)
		if err != nil {
			return nil, errors.New("crypto/ecdh: invalid public key")
		}
		k.boring = bk
	} else {
		fk, err := c.newPublicKey(key)
		if err != nil {
			return nil, err
		}
		k.fips = fk
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Provide the uncompressed SEC1 encoding: 0x04 || X || Y.
  2. If you only have a compressed point, decompress it first (e.g. via elliptic.UnmarshalCompressed) then re-encode uncompressed.
  3. Reject peer keys that do not start with 0x04 at the protocol boundary.

Example fix

// before
pub, err := curve.NewPublicKey(compressedKey) // compressedKey[0] == 0x02
// after
x, y := elliptic.UnmarshalCompressed(curve.Curve, compressedKey)
uncompressed := append([]byte{0x04}, append(x, y...)...)
pub, err := curve.NewPublicKey(uncompressed)
Defensive patterns

Strategy: validation

Validate before calling

func loadPub(curve ecdh.Curve, key []byte) (*ecdh.PublicKey, error) {
    if len(key) == 0 || key[0] != 0x04 {
        return nil, fmt.Errorf("public key must be uncompressed SEC1 (0x04 prefix), got %d bytes", len(key))
    }
    return curve.NewPublicKey(key)
}

Type guard

func isUncompressedSEC1(key []byte) bool {
    return len(key) > 0 && key[0] == 0x04
}

Try / catch

pub, err := curve.NewPublicKey(key)
if err != nil && len(key) > 0 && key[0] != 0x04 {
    // decompress first, then retry with 0x04 || X || Y
}

Prevention

When it happens

Trigger: Calling curve.NewPublicKey(key) where key is empty, or key[0] is 0x02/0x03 (compressed) or any non-0x04 value.

Common situations: Peer sends a compressed point; receiving an X9.62-encoded key with a different prefix; empty buffer from a failed read; serializing with a library that defaults to compressed form.

Related errors


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