astrid-runtime/astrid · error
decode public key hex
Error message
decode public key hex: {e} What it means
pubkey_hex_to_wire converts a 64-char hex Ed25519 public key into the `ed25519:<base64>` wire form using astrid_crypto::PublicKey::from_hex. If the hex string cannot be decoded into a 32-byte public key, the decode error is rethrown as "decode public key hex: {e}".
Solutions
- Ensure the input is the raw 64-hex-char public key; if you have the wire form, extract the base64 part and decode to hex instead
- Trim the string and strip prefixes/quotes before calling (the code already trims, but embedded whitespace breaks parsing)
- Regenerate the keypair and re-export the key if the stored value is corrupt
Example fix
// before
let wire = pubkey_hex_to_wire("ed25519:AbCd...")?; // already wire form
// after
let wire = pubkey_hex_to_wire("a1b2...64-hex-chars")?; Defensive patterns
Strategy: validation
Validate before calling
fn is_hex_pubkey(s: &str) -> bool {
let s = s.trim();
s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())
} Type guard
fn convertible_to_wire(s: &str) -> bool {
let s = s.trim();
s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())
} Try / catch
match pubkey_hex_to_wire(pub_hex) {
Ok(wire) => wire,
Err(e) if e.to_string().starts_with("decode public key hex") => {
eprintln!("Input must be 64 hex chars, not the ed25519:<base64> wire form");
return Err(e);
}
} Prevention
- Never pass the `ed25519:<base64>` wire form into hex-expecting functions
- Copy keys without truncation; validate 64 hex chars before storing
- Keep a single source of truth for the key and derive both forms from it
When it happens
Trigger: Called with a string that is not exactly 64 valid hex characters: wrong length, invalid hex digits, embedded whitespace inside the string, empty input, or a value already in base64/wire form.
Common situations: Pasting a `ed25519:<base64>` key where hex is expected (double conversion); truncated keys from terminal copy/paste; keys read from config with quotes or prefixes left in.
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
- fingerprint Ed25519 public key
- invalid ed25519 public key
- invalid ed25519 secret key
- public key must be in 'ed25519
- invalid runtime key (replaces 'invalid signing key' in…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/84c400daff5f1966.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/keypair.rs:616
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;
let mut blob = Vec::with_capacity(4 + 11 + 4 + 32);
let typ = b"ssh-ed25519";
blob.extend_from_slice(&u32::try_from(typ.len()).unwrap_or(0).to_be_bytes());
blob.extend_from_slice(typ);
blob.extend_from_slice(&u32::try_from(pubkey.len()).unwrap_or(0).to_be_bytes());
blob.extend_from_slice(pubkey);
format!(
"ssh-ed25519 {}",
base64::engine::general_purpose::STANDARD.encode(&blob)View on GitHub (pinned to affd8760f4)