golang/go · error
invalid PQ KEM for P-384 hybrid
Error message
invalid PQ KEM for P-384 hybrid
What it means
NewHybridPublicKey builds an ML-KEM + ECDH hybrid. For P-384 the only valid pairing is ML-KEM-1024 (combiner ID 0x0051). If the pq argument is not *mlkem.EncapsulationKey1024, the constructor rejects it. Note this branch differs from X25519/P-256, which require the 768-bit variant.
Source
Thrown at src/crypto/hpke/pq.go:156
//
// This function is meant for applications that already have instantiated
// crypto/ecdh and crypto/mlkem public keys. Otherwise, applications should use
// the [KEM.NewPublicKey] method of e.g. [MLKEM768X25519].
func NewHybridPublicKey(pq crypto.Encapsulator, t *ecdh.PublicKey) (PublicKey, error) {
switch t.Curve() {
case ecdh.X25519():
if _, ok := pq.(*mlkem.EncapsulationKey768); !ok {
return nil, errors.New("invalid PQ KEM for X25519 hybrid")
}
return &hybridPublicKey{mlkem768X25519, t, pq}, nil
case ecdh.P256():
if _, ok := pq.(*mlkem.EncapsulationKey768); !ok {
return nil, errors.New("invalid PQ KEM for P-256 hybrid")
}
return &hybridPublicKey{mlkem768P256, t, pq}, nil
case ecdh.P384():
if _, ok := pq.(*mlkem.EncapsulationKey1024); !ok {
return nil, errors.New("invalid PQ KEM for P-384 hybrid")
}
return &hybridPublicKey{mlkem1024P384, t, pq}, nil
default:
return nil, errors.New("unsupported curve")
}
}
func (kem *hybridKEM) NewPublicKey(data []byte) (PublicKey, error) {
if len(data) != kem.pqEncapsKeySize+kem.curvePointSize {
return nil, errors.New("invalid public key size")
}
pq, err := kem.pqNewPublicKey(data[:kem.pqEncapsKeySize])
if err != nil {
return nil, err
}
var k *ecdh.PublicKey
fips140.WithoutEnforcement(func() { // Hybrid of ML-KEM, which is Approved.
k, err = kem.curve.NewPublicKey(data[kem.pqEncapsKeySize:])View on GitHub (pinned to b6b368adc5)
Solutions
- Pair P-384 only with *mlkem.EncapsulationKey1024.
- Prefer MLKEM1024P384().NewPublicKey(data) for combined parsing.
- Document the per-curve ML-KEM size requirement at the call site.
Example fix
// before pq, _ := mlkem.NewEncapsulationKey768(pqBytes) hpkePub, err := hpke.NewHybridPublicKey(pq, p384Pub) // "invalid PQ KEM for P-384 hybrid" // after pq, _ := mlkem.NewEncapsulationKey1024(pqBytes) hpkePub, err := hpke.NewHybridPublicKey(pq, p384Pub)
Defensive patterns
Strategy: type-guard
Validate before calling
func p384HybridPub(pq crypto.Encapsulator, t *ecdh.PublicKey) (hpke.PublicKey, error) {
if _, ok := pq.(*mlkem.EncapsulationKey1024); !ok {
return nil, fmt.Errorf("P-384 hybrid requires *mlkem.EncapsulationKey1024, got %T", pq)
}
return hpke.NewHybridPublicKey(pq, t)
} Type guard
func isMLKEM1024Encapsulator(pq crypto.Encapsulator) bool {
_, ok := pq.(*mlkem.EncapsulationKey1024)
return ok
} Try / catch
pub, err := hpke.NewHybridPublicKey(pq, p384Pub)
if err != nil && err.Error() == "invalid PQ KEM for P-384 hybrid" {
return nil, fmt.Errorf("need *mlkem.EncapsulationKey1024, got %T", pq)
} Prevention
- Remember P-384 is the only combiner that pairs with ML-KEM-1024.
- Use MLKEM1024P384().NewPublicKey(blob) for parsing.
- Document the per-combiner ML-KEM size at the integration boundary.
When it happens
Trigger: Calling hpke.NewHybridPublicKey(pq, p384Pub) with pq being *mlkem.EncapsulationKey768 or any non-EncapsulationKey1024 Encapsulator.
Common situations: Assuming all hybrids use ML-KEM-768; reusing the same PQ key across X25519/P-256/P-384 code paths; copy-paste between combiner variants.
Related errors
- invalid PQ KEM for X25519 hybrid
- invalid PQ KEM for P-256 hybrid
- unsupported curve
- invalid public key size
- hpke: invalid hybrid KEM secret length
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/eef8b4a28cea4d94.
Report an issue: GitHub.