astrid-runtime/astrid · error
fingerprint Ed25519 public key
Error message
fingerprint Ed25519 public key: {e} What it means
fingerprint_pubkey converts a hex-encoded Ed25519 public key into the short `key-<hex>` fingerprint via astrid_crypto::PublicKeyFingerprint::from_ed25519_hex. When the input is not valid 64-char hex encoding of a 32-byte Ed25519 public key, the fingerprint parser fails and the error is wrapped as "fingerprint Ed25519 public key: {e}".
Solutions
- Pass the 64-char hex public key (not the `ed25519:<base64>` wire form) to fingerprinting; convert wire form back to hex first if needed
- Trim whitespace and strip any `0x` prefix or PEM headers from the key string before calling
- Regenerate the keypair if the stored key is corrupt (astrid keypair generate)
Example fix
// before let fp = fingerprint_pubkey(&wire)?; // "ed25519:AbCd..." not hex // after let fp = fingerprint_pubkey(&hex_pub.trim())?; // 64-char hex
Defensive patterns
Strategy: validation
Validate before calling
fn is_hex_ed25519_pubkey(s: &str) -> bool {
let s = s.trim();
s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())
} Type guard
fn fingerprintable(s: &str) -> bool {
let s = s.trim();
s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())
} Try / catch
match fingerprint_pubkey(hex_pub) {
Ok(fp) => fp,
Err(e) if e.to_string().starts_with("fingerprint Ed25519 public key") => {
eprintln!("Key is not 64-char hex; convert wire form or re-copy the key");
return Err(e);
}
} Prevention
- Store keys in one canonical form (hex for metadata, wire form only in config) and convert explicitly
- Trim whitespace/newlines when reading keys from files
- Validate key length/format before persisting key metadata
When it happens
Trigger: Called from run_generate, read_meta, or fingerprint_is_stable_and_distinct with a string that isn't exactly 64 lowercase/uppercase hex chars (wrong length, non-hex characters, empty string, or the `ed25519:<base64>` wire form passed instead of hex).
Common situations: Storing the base64 wire form in metadata and later fingerprinting it directly; truncated key from copy/paste; reading a key file with trailing whitespace/newlines or a PEM header.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- decode public key hex
- invalid ed25519 public key
- invalid ed25519 secret key
- public key must be in 'ed25519
- signing key must be exactly 32 raw bytes (got )
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/1674c64f6407d7b5.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/keypair.rs:607
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
{
bail!("keypair name {name:?} contains invalid chars; only a-z, 0-9, '-' are allowed");
}
Ok(())
}
fn default_name() -> String {
let mut bytes = [0u8; 4];
SysRng
.try_fill_bytes(&mut bytes)
.expect("OS CSPRNG unavailable while generating default keypair name");
format!("key-{}", hex::encode(bytes))
}
fn fingerprint_pubkey(hex_pub: &str) -> Result<String> {
PublicKeyFingerprint::from_ed25519_hex(hex_pub)
.map(PublicKeyFingerprint::into_inner)
.map_err(|e| anyhow::anyhow!("fingerprint Ed25519 public key: {e}"))
}
/// Convert a 64-char hex ed25519 public key into the `ed25519:<base64>`
/// wire form that `[distro.signing].pubkey`, `astrid distro seal`, and
/// the distro trust store consume. Reuses `astrid-crypto`'s encoder so
/// the base64 variant matches the verifier byte-for-byte.
fn pubkey_hex_to_wire(pub_hex: &str) -> Result<String> {
let pk = astrid_crypto::PublicKey::from_hex(pub_hex.trim())
.map_err(|e| anyhow::anyhow!("decode public key hex: {e}"))?;
Ok(format!("ed25519:{}", pk.to_base64()))
}
/// Encode a 32-byte ed25519 public key in the `OpenSSH` wire format
/// (`ssh-ed25519 <base64>` — RFC 8709 §4). Lets operators paste the
/// same key into `authorized_keys` if they want to reuse it for SSH.
/// The body is a length-prefixed type tag followed by the key.
fn encode_openssh_ed25519(pubkey: &[u8]) -> String {
use base64::Engine;View on GitHub (pinned to affd8760f4)