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

  1. Prefix the key with "ed25519:" exactly (lowercase, with the colon) before passing it, e.g. ed25519:<base64>.
  2. If you only have raw base64, construct the wire form programmatically: format!("ed25519:{}", b64).
  3. Regenerate the wire form with pubkey_to_wire from a valid PublicKey instead of hand-assembling the string.
  4. 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

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


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)