astrid-runtime/astrid · error
public key must be in 'ed25519
Error message
public key must be in 'ed25519:<base64>' form, got {wire:?} What it means
parse_pubkey expects public keys serialized as the wire string "ed25519:<base64>". If the input lacks the "ed25519:" prefix, the strip_prefix fails and this error is raised, echoing the received string in debug form. It guards against passing raw base64 keys, other key formats, or entirely wrong strings where a wire-form key is required.
Solutions
- Prefix the key with "ed25519:" exactly (lowercase, with the colon) before passing it, e.g. ed25519:<base64>.
- If you only have raw base64, construct the wire form programmatically: format!("ed25519:{}", b64).
- Regenerate the wire form with pubkey_to_wire from a valid PublicKey instead of hand-assembling the string.
- Check the config/flag value wasn't truncated so that only the base64 half remained.
Example fix
// before
parse_pubkey("MCowBQYDK2VwAyEA...")?; // missing prefix
// after
parse_pubkey("ed25519:MCowBQYDK2VwAyEA...")?; Defensive patterns
Strategy: validation
Validate before calling
anyhow::ensure!(wire.starts_with("ed25519:") && wire.len() > "ed25519:".len(),
"public key must be 'ed25519:<base64>', got {wire:?}"); Type guard
fn is_pubkey_wire(s: &str) -> bool {
s.strip_prefix("ed25519:").map_or(false, |b| !b.is_empty())
} Try / catch
match parse_pubkey(wire) {
Ok(pk) => pk,
Err(e) if e.to_string().contains("must be in 'ed25519:<base64>' form") => {
// auto-heal: caller passed bare base64
parse_pubkey(&format!("ed25519:{wire}"))?
}
Err(e) => return Err(e),
} Prevention
- Always produce key strings with pubkey_to_wire instead of hand-writing them.
- Keep the prefix exactly lowercase "ed25519:" with a colon.
- Validate config fields containing keys at load time with the prefix check.
When it happens
Trigger: Calling parse_pubkey with a string missing the ed25519: prefix — a bare base64 key, an ed25519 key prefixed differently (e.g. "Ed25519:" with wrong case), an empty string, or a value read from a config field that stores a different format. Also exercised by the pubkey_wire_roundtrips test.
Common situations: Users pasting a raw base64 public key from another tool into a CLI flag or config; hand-editing a config file and dropping the prefix; mixing up the public key with a signature or secret key.
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
- fingerprint Ed25519 public key
- invalid ed25519 public key
- invalid ed25519 secret key
- malformed Distro.sig (expected 64-byte hex)
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/d3ff5c4961c2c33c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/distro/sign.rs:79
pub(crate) fn lock_signing_digest(lock: &DistroLock) -> anyhow::Result<[u8; 32]> {
let bytes = canonical_lock_bytes(lock)?;
let mut hasher = blake3::Hasher::new();
hasher.update(SIG_DOMAIN_TAG);
hasher.update(&bytes);
Ok(*hasher.finalize().as_bytes())
}
/// Sign a lock with `keypair`, returning the hex `Distro.sig` contents.
pub(crate) fn sign_lock(lock: &DistroLock, keypair: &KeyPair) -> anyhow::Result<String> {
let digest = lock_signing_digest(lock)?;
let sig = keypair.sign(&digest);
Ok(sig.to_hex())
}
/// Parse the `ed25519:<base64>` wire form into a [`PublicKey`].
pub(crate) fn parse_pubkey(wire: &str) -> anyhow::Result<PublicKey> {
let b64 = wire.strip_prefix("ed25519:").ok_or_else(|| {
anyhow::anyhow!("public key must be in 'ed25519:<base64>' form, got {wire:?}")
})?;
PublicKey::from_base64(b64).map_err(|e| anyhow::anyhow!("invalid ed25519 public key: {e}"))
}
/// Render a [`PublicKey`] as `ed25519:<base64>`.
pub(crate) fn pubkey_to_wire(pk: &PublicKey) -> String {
format!("ed25519:{}", pk.to_base64())
}
/// Verify a hex `Distro.sig` against a lock and a public key.
///
/// # Errors
///
/// Returns an error if the signature is malformed (not 64 hex bytes) or
/// does not verify against the lock's signing digest under `pubkey`.
pub(crate) fn verify_lock(
lock: &DistroLock,
sig_hex: &str,View on GitHub (pinned to affd8760f4)