FiloSottile/age · error

failed to create hybrid public key: %v

Error message

failed to create hybrid public key: %v

What it means

After curve validation, EncodeHybridRecipient calls hpke.NewHybridPublicKey to combine the MLKEM768 encapsulation key and X25519 key. If that construction fails (nil/invalid key material, wrong key sizes, malformed encapsulation key), the error is wrapped as 'failed to create hybrid public key'.

Source

Thrown at plugin/encode.go:105

// identities that are compatible with native recipients.
func EncodeX25519Recipient(pk *ecdh.PublicKey) (string, error) {
	if pk.Curve() != ecdh.X25519() {
		return "", fmt.Errorf("wrong ecdh Curve")
	}
	return bech32.Encode("age", pk.Bytes())
}

// EncodeHybridRecipient encodes a native MLKEM768-X25519 recipient from a
// [crypto/mlkem.EncapsulationKey768] and a [crypto/ecdh.X25519] public key.
// It's meant for plugins that implement identities that are compatible with
// native recipients.
func EncodeHybridRecipient(pq *mlkem.EncapsulationKey768, t *ecdh.PublicKey) (string, error) {
	if t.Curve() != ecdh.X25519() {
		return "", fmt.Errorf("wrong ecdh Curve")
	}
	pk, err := hpke.NewHybridPublicKey(pq, t)
	if err != nil {
		return "", fmt.Errorf("failed to create hybrid public key: %v", err)
	}
	return bech32.Encode("age1pq", pk.Bytes())
}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Verify pq is non-nil and is a valid *mlkem.EncapsulationKey768 (Bytes() returns 1184 bytes).
  2. Regenerate keys with mlkem.NewEncapsulationKey768 / GenerateKey768 rather than hand-constructing.
  3. Check t.Bytes() is exactly 32 bytes of X25519 public key.
  4. Log the underlying wrapped error from hpke.NewHybridPublicKey for the specific cause.

Example fix

// before
var pq *mlkem.EncapsulationKey768 // nil
s, err := plugin.EncodeHybridRecipient(pq, t.PublicKey())
// after
pq, err := mlkem.NewEncapsulationKey768(pqBytes)
if err != nil { return err }
s, err := plugin.EncodeHybridRecipient(pq, t.PublicKey())
Defensive patterns

Strategy: try-catch

Validate before calling

if pq == nil || len(pq.Bytes()) != 1184 { return errors.New("invalid MLKEM768 encapsulation key") }
if t == nil || len(t.Bytes()) != 32 { return errors.New("invalid X25519 public key") }

Type guard

func validHybridInputs(pq *mlkem.EncapsulationKey768, t *ecdh.PublicKey) bool {
	return pq != nil && len(pq.Bytes()) == 1184 && t != nil && t.Curve() == ecdh.X25519()
}

Try / catch

s, err := plugin.EncodeHybridRecipient(pq, t)
if err != nil {
	return fmt.Errorf("EncodeHybridRecipient: %w", err) // log wrapped hpke cause
}

Prevention

When it happens

Trigger: Passing a nil or zero-value *mlkem.EncapsulationKey768, an encapsulation key not produced by mlkem key generation/decapsulation-key export, or key bytes that fail hpke.NewHybridPublicKey's internal validation.

Common situations: Deserializing PQ keys from untrusted/corrupted storage; passing an EncapsulationKey64 (MLKEM512) where 768 is required; constructing keys from truncated byte slices.

Related errors


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/c6a2067cc523f6ad. Report an issue: GitHub.