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
- Regenerate the SSH key pair with ssh-keygen -t ed25519 and re-share the public key.
- Validate the public key first with ssh.ParseAuthorizedKey and a test conversion before use.
- 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
- Generate keys with ssh-keygen -t ed25519 from standard OpenSSH tooling.
- Never hand-edit or truncate public key strings.
- Pre-validate recipient strings with ssh.ParseAuthorizedKey before encryption.
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
- SSH public key is not an Ed25519 key
- SSH public key is not an RSA key
- unexpected public key type
- pk does not implement ssh.CryptoPublicKey
- RSA key size is too small
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/bb8aef81620a3d57.
Report an issue: GitHub.