FiloSottile/age · error

invalid Ed25519 public key: %v

Error message

invalid Ed25519 public key: %v

What it means

NewEd25519Recipient converts an SSH Ed25519 public key to a Curve25519 key for X25519-based encryption. If that conversion (ed25519PublicKeyToCurve25519) fails, the key is reported as an invalid Ed25519 public key. A valid ed25519.PublicKey should convert, so failure implies a malformed or out-of-range key point.

Source

Thrown at agessh/agessh.go:164

var _ age.Recipient = &Ed25519Recipient{}

func NewEd25519Recipient(pk ssh.PublicKey) (*Ed25519Recipient, error) {
	if pk.Type() != "ssh-ed25519" {
		return nil, errors.New("SSH public key is not an Ed25519 key")
	}

	cpk, ok := pk.(ssh.CryptoPublicKey)
	if !ok {
		return nil, errors.New("pk does not implement ssh.CryptoPublicKey")
	}
	epk, ok := cpk.CryptoPublicKey().(ed25519.PublicKey)
	if !ok {
		return nil, errors.New("unexpected public key type")
	}
	mpk, err := ed25519PublicKeyToCurve25519(epk)
	if err != nil {
		return nil, fmt.Errorf("invalid Ed25519 public key: %v", err)
	}

	return &Ed25519Recipient{
		sshKey:         pk,
		theirPublicKey: mpk,
	}, nil
}

func ParseRecipient(s string) (age.Recipient, error) {
	pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s))
	if err != nil {
		return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err)
	}

	var r age.Recipient
	switch t := pubKey.Type(); t {
	case "ssh-rsa":
		r, err = NewRSARecipient(pubKey)

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Regenerate the SSH key pair with ssh-keygen -t ed25519 and re-share the public key.
  2. Validate the public key first with ssh.ParseAuthorizedKey and a test conversion before use.
  3. Check the key string for truncation/whitespace corruption when copying between systems.

Example fix

// before
r, err := agessh.NewEd25519Recipient(corruptedPubKey) // conversion fails
// after
if _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKeyStr)); err != nil {
    return fmt.Errorf("invalid public key: %w", err)
}
r, err := agessh.NewEd25519Recipient(parsedKey)
Defensive patterns

Strategy: validation

Validate before calling

pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s))
if err != nil || pubKey.Type() != "ssh-ed25519" {
    return errors.New("not a valid ssh-ed25519 public key")
}

Type guard

func isEd25519Recipient(s string) bool {
    k, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s))
    return err == nil && k.Type() == "ssh-ed25519"
}

Try / catch

r, err := agessh.NewEd25519Recipient(pk)
if err != nil {
    return fmt.Errorf("cannot use SSH key for age: %w", err)
}

Prevention

When it happens

Trigger: Calling agessh.NewEd25519Recipient (directly or via ParseRecipient) with an ssh-ed25519 public key whose conversion to Curve25519 fails — e.g. a non-canonical or corrupted key point, or an unexpected key type sneaking in earlier ("unexpected public key type").

Common situations: Hand-edited or corrupted authorized_keys entries; keys generated by non-standard tooling; paste errors truncating or altering the base64 key body.

Related errors


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