FiloSottile/age · error
wrong ecdh Curve
Error message
wrong ecdh Curve
What it means
EncodeX25519Recipient converts a crypto/ecdh X25519 public key into a native 'age1...' recipient string for plugins wrapping compatible identities. It refuses any ecdh.PublicKey whose curve is not X25519, since the bech32 payload would otherwise be a key of the wrong type/length.
Source
Thrown at plugin/encode.go:90
func validPluginName(name string) bool {
if name == "" {
return false
}
allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-._"
for _, r := range name {
if !strings.ContainsRune(allowed, r) {
return false
}
}
return true
}
// EncodeX25519Recipient encodes a native X25519 recipient from a
// [crypto/ecdh.X25519] public key. It's meant for plugins that implement
// 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
- Create the key with ecdh.X25519(): e.g. ecdh.X25519().NewPublicKey(pubBytes).
- Check pk.Curve() == ecdh.X25519() before calling.
- Use a curve-specific encoder if the key is NIST/X448.
- Surface a clear error distinguishing curve types at the API boundary.
Example fix
// before
pk, _ := ecdh.P256().NewPublicKey(pubBytes)
s, err := plugin.EncodeX25519Recipient(pk)
// after
pk, err := ecdh.X25519().NewPublicKey(pubBytes)
if err != nil { return err }
s, err := plugin.EncodeX25519Recipient(pk) Defensive patterns
Strategy: type-guard
Validate before calling
if pk.Curve() != ecdh.X25519() { return fmt.Errorf("need X25519 key, got %v", pk.Curve()) } Type guard
func isX25519(pk *ecdh.PublicKey) bool { return pk != nil && pk.Curve() == ecdh.X25519() } Try / catch
s, err := plugin.EncodeX25519Recipient(pk)
if err != nil {
return fmt.Errorf("EncodeX25519Recipient: %w (curve=%v)", err, pk.Curve())
} Prevention
- Construct keys only via ecdh.X25519().NewPublicKey/GenerateKey
- Check Curve() at function boundaries when keys flow through generic code
- Name variables by curve (x25519Pub, p256Pub) to avoid mixups
When it happens
Trigger: Passing an ecdh.PublicKey created with ecdh.P256(), ecdh.P384(), ecdh.P521(), or ecdh.X448() instead of ecdh.X25519().
Common situations: Generic ECDH helper code that selects curves by config; refactoring from P256 to X25519 (or vice versa) but reusing the same key variable; plugins that support multiple curves calling the wrong encoder.
Related errors
- failed to decrypt and authenticate final chunk: %w
- failed to decrypt and authenticate chunk at offset %d: %w
- failed to create hybrid public key: %v
- invalid tag recipient public key size %d
- invalid tag recipient public key: %v
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/aa42ec9e7fd7b4ec.
Report an issue: GitHub.