astrid-runtime/astrid · error
invalid ed25519 public key
Error message
invalid ed25519 public key: {e} What it means
The second stage of parse_pubkey: after the ed25519: prefix is stripped, the remaining base64 is handed to PublicKey::from_base64. If the crypto layer cannot decode it into a valid ed25519 public key (bad base64 characters, wrong length, invalid point), the error is wrapped as "invalid ed25519 public key: {e}".
Solutions
- Re-copy the public key value carefully — verify it base64-decodes to exactly 32 bytes.
- Regenerate the wire string via pubkey_to_wire(pk) from a key you hold rather than manual copy/paste.
- Ensure you are using the public key, not the secret key or a signature, in this field.
- Strip whitespace/newlines from the value before passing it (trim in scripts).
Example fix
// before
parse_pubkey(&conf.publisher_key.trim_end_matches('"'))?; // value corrupted by editor
// after
let b64: String = conf.publisher_key.trim().chars().filter(|c| !c.is_whitespace()).collect();
parse_pubkey(&format!("ed25519:{b64}"))?; Defensive patterns
Strategy: validation
Validate before calling
use base64::Engine;
let b64 = wire.strip_prefix("ed25519:").context("missing ed25519: prefix")?;
let raw = base64::engine::general_purpose::STANDARD.decode(b64.trim())?;
anyhow::ensure!(raw.len() == 32, "public key must decode to 32 bytes, got {}", raw.len()); Type guard
fn valid_pubkey_wire(s: &str) -> bool {
s.strip_prefix("ed25519:")
.and_then(|b| base64::engine::general_purpose::STANDARD.decode(b.trim()).ok())
.map_or(false, |raw| raw.len() == 32)
} Try / catch
match parse_pubkey(wire) {
Ok(pk) => pk,
Err(e) if e.to_string().contains("invalid ed25519 public key") => {
eprintln!("key payload is not valid base64 / not 32 bytes; re-copy the key");
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- Trim whitespace and strip quotes/newlines from pasted keys before parsing.
- Verify the value base64-decodes to 32 bytes in config validation.
- Use pubkey_to_wire output round-tripped through pubkey_wire_roundtrips-style tests.
When it happens
Trigger: Calling parse_pubkey with a well-prefixed "ed25519:<b64>" string whose payload is not valid base64 or does not decode to a 32-byte valid ed25519 public key — truncated keys, whitespace/typo corruption, or a signature/secret pasted where a public key belongs. Also hit by the pubkey_wire_roundtrips test.
Common situations: Copy-paste truncation of a long key string; smart quotes or line breaks introduced by editing a config in a rich-text editor; using a key from a different curve/tool that base64-decodes to the wrong length.
Related errors
- invalid ed25519 secret key
- decode public key hex
- fingerprint Ed25519 public key
- public key must be in 'ed25519
- invalid base64 filesystem payload
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/5f5d10644b5b5062.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/distro/sign.rs:81
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,
pubkey: &PublicKey,
) -> anyhow::Result<()> {View on GitHub (pinned to affd8760f4)