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

  1. Create the key with ecdh.X25519(): e.g. ecdh.X25519().NewPublicKey(pubBytes).
  2. Check pk.Curve() == ecdh.X25519() before calling.
  3. Use a curve-specific encoder if the key is NIST/X448.
  4. 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

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


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