astrid-runtime/astrid · error

principal has an invalid genesis public key

Error message

principal {alias} has an invalid genesis public key: {error}

What it means

Thrown by genesis_public_key_bytes when the selected device key's hex string cannot be decoded into an astrid_crypto::PublicKey via from_hex. The profile selected a key (so one exists) but its pubkey bytes are not a valid Ed25519 public key in hex — wrong length or non-hex characters. The underlying decode error is embedded in the message.

Solutions

  1. Fix the device.pubkey field so it contains a valid Ed25519 public key as plain hex of the expected length.
  2. Re-export the profile from the legacy application so it writes a correct key.
  3. Remove/quarantine the principal if the key cannot be recovered, instead of minting it.
  4. Validate profile keys with PublicKey::from_hex before starting migration to catch bad entries early.

Example fix

// before
"pubkey": "0xAB12CD..."          // 0x prefix, invalid
// after
"pubkey": "ab12cd34..."          // plain lowercase hex, 64 chars for Ed25519
Defensive patterns

Strategy: validation

Validate before calling

fn pubkey_is_valid_hex(s: &str) -> bool {
    astrid_crypto::PublicKey::from_hex(s).is_ok()
}

Try / catch

match mint_valid_leftover(entry) {
    Err(e) if e.to_string().contains("invalid genesis public key") => {
        // re-export or quarantine the principal with the bad key
        quarantine(entry)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: genesis_public_key_bytes (via mint_valid_leftover) calls PublicKey::from_hex(&device.pubkey) on the earliest device key and hex decoding or key-length validation fails.

Common situations: A profile with a truncated or padded pubkey string from a buggy legacy writer; a placeholder like "<pubkey>" or a "0x..." prefix in hand-edited JSON; a key of a different algorithm stored in the pubkey field; whitespace/case issues if from_hex is strict.

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/e32d1a9a4c3a583d. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-kernel/src/principal_home_migration/unbound.rs:222

    Ok(true)
}

fn genesis_public_key_bytes(home: &AstridHome, alias: &PrincipalId) -> io::Result<[u8; 32]> {
    let profile =
        PrincipalProfile::load_required(home, alias).map_err(|error| profile_io(&error))?;
    let device = profile
        .auth
        .public_keys
        .iter()
        .min_by_key(|device| (device.created_at, device.key_id.as_str()))
        .ok_or_else(|| {
            io::Error::new(
                io::ErrorKind::InvalidData,
                format!("principal {alias} has no Ed25519 key for genesis identity"),
            )
        })?;
    let public_key = astrid_crypto::PublicKey::from_hex(&device.pubkey).map_err(|error| {
        io::Error::new(
            io::ErrorKind::InvalidData,
            format!("principal {alias} has an invalid genesis public key: {error}"),
        )
    })?;
    Ok(public_key.into())
}

fn quarantine_entry(
    home: &AstridHome,
    source: &Path,
    file_name: &OsStr,
    reason: &str,
) -> io::Result<()> {
    let quarantine_root = home.migrations_dir().join(QUARANTINE_DIR);
    astrid_core::platform_fs::ensure_private_directory(&quarantine_root)?;
    let destination = unique_quarantine_path(&quarantine_root, file_name)?;
    let source_parent = source.parent().map(Path::to_path_buf);
    fs::rename(source, &destination).map_err(|error| {

View on GitHub (pinned to affd8760f4)